发布时间

GO 可以到达的地方

结构体

结构体的定义是开发自行定义的类型,使用关键字 struct 定义类型,在Go中没有类的概念,与js、python、Java中的class是一个意思。在定义中字段名称的大小写决定了该字段的可见性(即可访问性)

  • 字段名以大写字母开头,那么该字段是公开的(public),可以被其他包中的代码访问。
  • 字段名以小写字母开头,那么该字段是私有的(private),只能在本包内访问。
type Person struct {
    Name   string  // 可被外部包访问
    Age    int     // 可被外部包访问
    Email  string  // 可被外部包访问
}

type person struct {
    name    string  // 只能在包内访问
    age     int     // 只能在包内访问
    secret  string  // 只能在包内访问
}

// 私有方法
func (p *person) validate() error {
    // 只能在包内调用
}

type Person struct {
  Name string
  Age    int
  Sex  string
}

var p4 = Person{
  Name:“哈哈",
  Age: 20,
  Sex: "男"
}
fmt.Printf("值:%#v 类型:%T\n",p4,p4)

var p5 = &Person{
  Name:"王麻子”,
  Age: 20  Sex:"男",
}

注意 ":"为地址赋值属于值类型,"&"为指针赋值属于引用类型

值类型:改变变量副本值的时候,不会改变变量本身的值(数组、基本数据类型、结构体)
引用类型:改变变量副本值的时候,!会改变变量本身的值(切片、map)

定义方法

# 定义方式
func (接受者变量 接受者类型) 方法名(参数列表) (返回参数){
   函数体
}
// user.go (在 package models 中)
package models

type User struct {
    ID       int     // 导出字段
    Username string  // 导出字段
    password string  // 私有字段
    email    string  // 私有字段
}
// 可导出的方法
func (p *Person) GetName() string {
    return p.Name
}

// 导出方法 即User.SetPassword()
func (u *User) SetPassword(pass string) {
    u.password = hashPassword(pass)  // 内部可以访问私有字段
}

// 私有方法
func (u *User) validateEmail() bool {
    return strings.Contains(u.email, "@")  // 内部可以访问私有字段
}

// main.go (在 package main 中)
package main

import "models"

func main() {
    user := models.User{
        ID:       1,
        Username: "john",  // ✓ 可以访问
        password: "secret", // ✗ 编译错误:未导出字段
    }
    
    user.SetPassword("newpass")  // ✓ 可以调用导出方法
    user.validateEmail()         // ✗ 编译错误:未导出方法
}

值接收者 vs 指针接收者(方法上加*)的区别

特性值接收者指针接收者
修改原值❌ 不能修改✅ 可以修改
内存开销复制整个结构体只复制指针(8字节)
nil 处理不能为 nil可以为 nil
并发安全更安全需要额外同步
// 值接收者
func (s StructName) MethodName() {
    // s 是原结构体的副本
}

// 指针接收者
func (s *StructName) MethodName() {
    // s 是指向原结构体的指针
}

type User struct {
    Name string
    Age  int
}

// 值接收者 - 不能修改原结构体
func (u User) UpdateNameValue(newName string) {
    u.Name = newName
    fmt.Printf("Inside value method: %s\n", u.Name)
}

// 指针接收者 - 可以修改原结构体
func (u *User) UpdateNamePointer(newName string) {
    u.Name = newName
    fmt.Printf("Inside pointer method: %s\n", u.Name)
}

func main() {
    user := User{Name: "Alice", Age: 25}
    // 值接收者 - 原值不变
    user.UpdateNameValue("Bob")
    fmt.Printf("After value method: %s\n", user.Name)
    // Inside value method: Bob
    // After value method: Alice
    
    // 指针接收者 - 原值改变
    user.UpdateNamePointer("Charlie")
    fmt.Printf("After pointer method: %s\n", user.Name)
    // Inside pointer method: Charlie
    // After pointer method: Charlie
}

& 与 * 对比

运算符含义示例作用
&取地址&x获取变量 x 的内存地址
*解引用*ptr获取指针 ptr 指向的值

& 的注意事项

func main() {
    x := &42         // ❌ 编译错误:不能获取字面量的地址
    y := &"hello"    // ❌ 编译错误
    
    // 正确做法
    num := 42
    x := &num        // ✅
    
    str := "hello"
    y := &str        // ✅
}
func createValue() int {
    return 42
}

func main() {
    ptr := &createValue()  // ❌ 编译错误:不能获取函数返回值的地址
}
func main() {
    m := map[string]int{"a": 1}
    ptr := &m["a"]  // ❌ 编译错误:不能获取map元素的地址
}

匿名结构体|嵌套结构体

结构体的匿名字段:
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段,匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿不能定义。

结构体的字段类型可以是:基本数据类型、也可以是切片、Map 以及结构体。
如果结构体的字段类型是:指针,slice,和map的零值都是 nil,即还没有分配空间如果需要使用这样的字段,需要先make,才能使用。

当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找

type User struct {
  Username string
  Password string
  AddTime  string
  Address  Address
}
type Address struct {
  Name string
  Phone string
  city  string
  AddTime  string
}

var u User
u.Username = "hello"
u.city = "桂林"
u.AddTime = "2025-12-03"
// {name:"hello",Password:"",AddTime:"2025-12-03",Address:{Name:"",Phone:"",city:"桂林",AddTime:""}}

Go 语言中使用结构体也可以实现其他编程语言中的继承。上面的User结构体中第一层找不到对应的AddTime,会在子结构体中查找,但是在多个子结构体中有相同字段,若是没有指定对应的结构体名称会报错,go无法知道是哪个字段需要继承赋值

type Animal struct {
  Name string
}
func(a Animal)run(){
  fmt.Printf("%v 在运动\n",a.Name)
}
//子结构体
type Dog struct {
  Age int
  Animal //结构体嵌套 继承
}
func(d Dog)wang(){
  fmt.Printf("%v 在旺旺\n",d.Name)
}
func main(){
  var d = Dog{
    Age: 20,
    Animal: Animal{
      Name:"阿奇"
    }
  }
  d.run()  //继承了Animal的方法
  d.wang()
}

转JSON

import (
    "encoding/json"
    "fmt"
)

type Student struct {
  ID  int
  Gender string
  name  string//私有属性不能被json包访问
  Sno   string
}
func main(){
  var s1 = Student{
	ID:     12,
	Gender: "男",
	Name:   "李四",
	Sno:    "s0001",
  }
  fmt.Printf("%#v\n", s1)
  jsonByte, _ := json.Marshal(s1)
  jsonStr := string(jsonByte)
  fmt.Printf("%v", jsonStr)

  // 反序列化
  var s2 Student
  err := json.Unmarshal([]byte(jsonStr), &s2)
  if err != nil {
    fmt.Println(err)
  }
  fmt.Printf("%#v\n", s1)
}

配置JSON自定义key

type Student struct {
  ID  int   json:"id"   // 用``包裹  转字符串就是id,而不是ID
  Gender string  json:"gender"  Name  string json:"name"// 转义的是name
  Sno   string  json:"son"//转义的是son
}

mod 与 golang包

项目中的.mod文件是在项目上执行 go mod init <项目名称>自定义创建的,有点像js中创建项目的npm init,文件中定义的包名就是后续在项目中引用自定义包名的前缀名称

包(package)是多个 Go 源码的集合,一个包可以简单理解为一个存放多个.go 文件的文件夹。该文件夹下面的所有 g0 文件都要在代码的第一行添加如下代码,声明该文件归属的包。

package <包名>

  • 一个文件夹下面直接包含的文件只能归属一个 package,同样一个 package 的文件不能在多个文件夹下。
  • 包名可以不和文件夹的名字一样,包名不能包含 -符号。
  • 包名为 main 的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含 main 包的源代码则不会得到可执行文件。
demo
|__utils
|   |__cale.go  //定义 package 名称建议和 utils 一致
|__tools
|   |__math.go   // 一个tools 下可以有多个相同的 package 名文件
|___main.go

# main.go
import (
  "demo/utils"
  T "demo/tools" // 创建别名 T
  _ "demo/tools" // 支持创建匿名 _
)
func main (){
  utils.cale()
  T.add()
}

执行顺序

在执行的文件中定义了init()方法,在执行顺序的时候是优先被系统调用,然后执行main()里面的代码,不管init()方法在main方法下,还是在文件的最下方都是第一执行顺序。

若是在A文件执行的方法引入了B文件方法b,b又引入了C文件的方法c,c方法又引入了D文件的d方法。然而A、B、C、D文件都定义了init()方法,那么执行的init顺序是D文件的先执行,最后到A文件的init方法执行。由此结论是:

在运行时,被最后导入的包会最先初始化并调用其init()函数

第三方包

看下go mod 常用命令

Usage:

        go mod <command> [arguments]

The commands are:

        download    download modules to local cache
        edit        edit go.mod from tools or scripts
        graph       print module requirement graph
        init        initialize new module in current directory
        tidy        add missing and remove unused modules
        vendor      make vendored copy of dependencies
        verify      verify dependencies have expected content
        why         explain why packages or modules are needed

第三方包地址:https://pkg.go.dev/

# float 精度损失的包 decimal
https:github.com/shopspring/decimal
go get github.com/shopspring/decimal
或者
go install github.com/shopspring/decimal@latest
或者
go mod tidy  //默认加载项目中引入的依赖

接口定义与实现

在 Golang 中接口中的所有方法都没有方法体,接口定义了一个对象的行为规范,只定义规范不实现。接口体现了程序设计的多态(同一接口可以被不同类型实现)和高内聚低耦合的思想。
Golang 中的接口也是一种数据类型,不需要显示实现。只需要一个变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。

// 定义接口
type 接口名 interface {
    方法名1(参数列表) 返回值列表
    方法名2(参数列表) 返回值列表
    // ...
}

// 定义动物接口
type Animal interface {
    Speak() string
    Move() string
    Eat(food string) string
}

// 定义 Dog 类型
type Dog struct {
    Name string
    Breed string
}

// 实现 Animal 接口的方法
func (d Dog) Speak() string {
    return "Woof! Woof!"
}

func (d Dog) Move() string {
    return "Running on four legs"
}

func (d Dog) Eat(food string) string {
    return fmt.Sprintf("%s is eating %s", d.Name, food)
}
func main() {
    var animal Animal
    
    // Dog 实现了 Animal 接口
    dog := Dog{Name: "Buddy", Breed: "Golden Retriever"}
    animal = dog
    fmt.Println(animal.Speak())  // Woof! Woof!
    fmt.Println(animal.Eat("bone"))  // Buddy is eating bone

}

接口的实现就有点像对象或者是类的继承,但是go实现的方式和其他语言就有点反过来了,先定义一个结构体(对象或者是类),将结构体赋值给定义的接口变量就相当于接口变量实现了继承。

空接口 interface{} 可以接收任何类型的值。

func printAnything(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

func main() {
    printAnything(42)              // int
    printAnything("hello")         // string
    printAnything(Dog{Name: "Max"}) // main.Dog
}

类型断言和类型判断

func processAnimal(animal Animal) {
    // 类型断言
    if dog, ok := animal.(Dog); ok {
        fmt.Printf("It's a %s dog named %s\n", dog.Breed, dog.Name)
    } else if cat, ok := animal.(Cat); ok {
        fmt.Printf("It's a %s cat named %s\n", cat.Color, cat.Name)
    }
    
    // 类型判断 (Go 1.18+)
    switch a := animal.(type) {
    case Dog:
        fmt.Printf("Dog speaking: %s\n", a.Speak())
    case Cat:
        fmt.Printf("Cat speaking: %s\n", a.Speak())
    default:
        fmt.Println("Unknown animal")
    }
}

func MyPrint(x interface{}){
	if _,ok := x.(string); ok {
		fmt.Println("string类型")
	}else if _,ok := x.(int); ok {
		fmt.Println("int类型")
	}else if _,ok := x.(bool); ok {
		fmt.Println("bool类型")
	}else {
		fmt.Println("其他类型")
	}
}

func main(){
	MyPrint("hello")
	MyPrint(123)
	MyPrint(true)
}

结构体值接收者和指针接收者实现接口的区别

  • 值接收者:如果结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量
  • 指针接收者:如果结构体中的方法是指针接收者那么实例化后结构体指针类型都可以赋值给接口变量,结构体值类型没法赋值给接口变量。

多接口|接口嵌套


type Animal interface {
	Speak() string
	Eat(food string) string
}

type Animal2 interface {
	Name string
	GetName() string
}

type Dog struct {
	Name string
	Breed string
}

func (d Dog) Speak() string {
	return "Woof! Woof!"
}

func (d Dog) Eat(food string) string {
	return d.Name + " is eating " + food
}

func (d Dog) GetName() string {
	return d.Name
}

func main() {   
    // Dog 实现 Animal 接口
    dog := Dog{Name: "Buddy", Breed: "Golden Retriever"}
    var d animal = dog   // 表示让dog实现Animal接口
	var d2 Animal2 = dog  // 表示让dog实现Animal2接口
    d.Speak()
    d.Eat("bone")
    d2.GetName()
}

接口嵌套:即在接口内定义一个变量使用另一个接口

协程与主线程

golang 中的主线程:(可以理解为线程/也西以理解为进程),在一个 Golang 程序的主线程上可以起多个协程。Golang 中多协程可以实现并行或者并发。
协程:可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的。Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go关键字就可创建一个协程。可以说 Golang 中的协程就是goroutine 。

多协程和多线程:Golang 中每个 goroutine (协程)默认占用内存远比 Java 、℃ 的线程少。OS 线程(操作系统线程)一般都有固定的栈内存(通常为2MB 左右),一个goroutine(协程)占用内存非常小,只有 2KB 左右,多协程 goroutine 切换调度开销方面远比线程要少。这也是为什么越来越多的大公司使用 Golang 的原因之一。

注意:
1、主线程执行完毕后即使协程没有执行完毕程序也会退出。

2、协程可以在主线程没有执行完毕前提前退出,协程是否执行完毕不会影响主线程的执行。
为了保证我们的程序可以顺利执行我们想让协程执行完毕后在执行主进程退出,这个时候我们可以使用sync.WaitGroup等待协程执行完毕

import (
    "fmt"
    "time"
)
func say(s string, n int) {
	var t = 100 * n
	for i := 0; i < 3; i++ {
		time.Sleep(time.Duration(t) * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("goroutine", 10) // 启动协程
	say("main", 6)          // 主协程执行
}

协程 vs 传统线程

特性协程(Goroutine)传统线程
创建成本极低(KB 级)较高(MB 级)
调度方Go 运行时操作系统内核
切换成本用户态切换,极低内核态切换,较高
通信方式Channel(推荐)锁、信号量等

GOMAXPROCS 环境变量决定使用多少个操作系统线程来执行协程(默认是 CPU 核心数)。

管道 channel

Golang 的并发模型是 CSP(Communicating Sequentia Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
Go 语言中的管道(channel)是一种特殊的类型。管道像一个传送带或者队列,总是遵循先入先出(First In First out)的规则,保证收发数据的顺序。每一个管道都是一个具体类型的导管,也就是声明 channel的时候需要为其指定元素类型。

// 管道已经存满数据 all goroutines are asleep - deadlock!

// 管道没有数据 fatal error: all goroutines are asleep - deadlock!

func main() {
	//使用for range遍历通道,当通道被关闭的时候就会退出for range,如果没有关闭管道就会报个错误fatal error:
	var ch1 = make(chan int, 10)
	//for range循环遍历管道的值,注意:管道没有key
	for i := 1; i <= 10; i++ {
		ch1 <- i
	}
	close(ch1) //关闭管道 不关闭管道会报个错误fatal error: all goroutines are asleep - deadlock!
	for v := range ch1 {
		fmt.Println(v)
	}

	//2、通过for循环遍历管道的时候管道可以不关闭
	var ch2 = make(chan int, 10)
	for i := 1; i <= 10; i++ {
		ch2 <- i
	}
	for j := 0; j < 10; j++ {
		fmt.Println(<-ch2)
	}
}

需求 1:定义两个方法,一个方法给管道里面写数据,一个给管道里面读取数据。要求同步进行。
1、开启一个 fn1 的的协程给向管道 inchan 中写入 100 条数据
2、开启一个 fn2 的协程读取 inchan 中写入的数据
3、注意:fn1 和 fn2 同时操作一个管道
4、主线程必须等待操作完成后才可以退出

var wg sync.WaitGroup

func fn1(ch chan int) {
	for i := 1; i <= 10; i++ {
		ch <- i
		fmt.Printf("【写入】数据%v成功\n", i)
		time.Sleep(time.Millisecond * 500)
	}
	close(ch)
	wg.Done()
}
func fn2(ch chan int) {
	for v := range ch {
		fmt.Printf("【读取】数据%v成功\n", v)
		time.Sleep(time.Millisecond * 10)
	}
	wg.Done()
}
func main() {
	var ch = make(chan int, 10)
	wg.Add(2)
	go fn1(ch)
	go fn2(ch)
	wg.Wait()
}

在上面的代码中写入数据比读取数据慢,但是不影响代码的执行,因为在读取数据的时候是会等待数据的写入,因为数据在管道中的安全的

// 任务结构体
type Task struct {
	ID   int
	Data string
}

// 结果结构体
type Result struct {
	TaskID int
	Output string
}

func worker(id int, tasks <-chan Task, results chan<- Result, wg *sync.WaitGroup) {
	defer wg.Done()
	
	for task := range tasks {
		fmt.Printf("Worker%d 开始处理任务%d: %s\n", id, task.ID, task.Data)
		// 模拟处理耗时
		time.Sleep(time.Millisecond * time.Duration(100+task.ID*10))
		// 生成结果
		result := Result{
			TaskID: task.ID,
			Output: fmt.Sprintf("任务%d处理完成", task.ID),
		}
		
		results <- result
		fmt.Printf("Worker%d 完成处理任务%d\n", id, task.ID)
	}
}

func main() {
	// 创建任务和结果通道
	taskChan := make(chan Task, 10)
	resultChan := make(chan Result, 10)
	
	// 创建WaitGroup
	var wg sync.WaitGroup
	
	// 启动3个worker协程
	workerCount := 3
	wg.Add(workerCount)
	for i := 1; i <= workerCount; i++ {
		go worker(i, taskChan, resultChan, &wg)
	}
	// 启动结果收集协程
	go func() {
		for result := range resultChan {
			fmt.Printf("收到结果: 任务ID=%d, 输出=%s\n", 
				result.TaskID, result.Output)
		}
	}()
	// 发送10个任务
	go func() {
		for i := 1; i <= 10; i++ {
			task := Task{
				ID:   i,
				Data: fmt.Sprintf("数据%d", i),
			}
			taskChan <- task
			fmt.Printf("已发送任务%d\n", i)
		}
		close(taskChan) // 关闭任务通道,worker会退出
	}()
	
	// 等待所有worker完成
	wg.Wait()
	close(resultChan) // 关闭结果通道
	fmt.Println("所有任务处理完成")
}

单向管道

// 参数 chan<- int 表示只写管道
func producer(writeOnlyChan chan<- int) {
	for i := 1; i <= 5; i++ {
		fmt.Printf("生产者发送: %d\n", i)
		writeOnlyChan <- i
		time.Sleep(100 * time.Millisecond)
	}
	close(writeOnlyChan) // 只有发送方可以关闭管道
}

// 参数 <-chan int 表示只读管道
func consumer(readOnlyChan <-chan int) {
	for num := range readOnlyChan {
		fmt.Printf("消费者接收: %d\n", num)
		time.Sleep(200 * time.Millisecond)
	}
}

func main() {
	// 创建双向管道
	ch := make(chan int, 3)

	// 启动生产者和消费者
	go producer(ch) // ch 被隐式转换为只写管道
	consumer(ch)    // ch 被隐式转换为只读管道

	fmt.Println("程序结束")
}

有的时候我们会将管道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用管道都会对其进行限制,比如限制管道在函数中只能发送或只能接收。

select多路复用

在某些场景下我们需要同时从多个通道接收数据,这个时候就可以用到golang中给我们提供的select多路复用。使用select来获取channe里面的数据的时候不需要关闭channe

func main() {
	normalChan := make(chan string, 5)  // 普通任务通道
	urgentChan := make(chan string, 5)  // 紧急任务通道
	shutdownChan := make(chan struct{}) // 关闭信号通道

	// 模拟任务产生
	go func() {
		for i := 1; i <= 10; i++ {
			if i%3 == 0 { // 每3个任务有一个紧急任务
				urgentChan <- fmt.Sprintf("紧急任务%d", i)
				fmt.Printf("产生紧急任务%d\n", i)
			} else {
				normalChan <- fmt.Sprintf("普通任务%d", i)
				fmt.Printf("产生普通任务%d\n", i)
			}
			time.Sleep(200 * time.Millisecond)
		}

		// 3秒后发送关闭信号
		time.Sleep(3 * time.Second)
		close(shutdownChan)
	}()

	// 任务处理器
	processor := func() {
		for {
			select {
			case urgentTask := <-urgentChan:
				// 优先处理紧急任务
				fmt.Printf("🔥 处理紧急: %s\n", urgentTask)
				time.Sleep(300 * time.Millisecond)

			case normalTask := <-normalChan:
				// 处理普通任务
				fmt.Printf("处理普通: %s\n", normalTask)
				time.Sleep(500 * time.Millisecond)

			case <-shutdownChan:
				fmt.Println("收到关闭信号")
				return

			case <-time.After(1 * time.Second):
				// 超时检查
				fmt.Println("等待任务中...")
			}
		}
	}

	// 启动两个处理器
	go processor()
	go processor()

	// 等待关闭
	<-shutdownChan
	time.Sleep(1 * time.Second) // 给处理器时间完成
	fmt.Println("程序结束")
}

互斥锁

Mutex(互斥锁)当需要共享内存时使用。互斥锁是传统并发编程中对共享资源进行访问控制的主要手段,它由标准库 sync 中的 Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法,Lock和 Unlock。Lock 锁定当前的共享资源,Unlock 进行解锁

var mu sync.Mutex
var counter int
go func() {
    mu.Lock()
    counter++
    mu.Unlock()
}()
package main

import (
	"fmt"
	"sync"
	"time"
)

type BankAccount struct {
	balance int
	mu      sync.Mutex // 互斥锁
}

func (b *BankAccount) Deposit(amount int) {
	b.mu.Lock()         // 获取锁
	defer b.mu.Unlock() // 确保释放锁
	
	// 临界区
	fmt.Printf("存款: %d, 当前余额: %d\n", amount, b.balance)
	b.balance += amount
}

func (b *BankAccount) Withdraw(amount int) bool {
	b.mu.Lock()
	defer b.mu.Unlock()
	
	if b.balance >= amount {
		fmt.Printf("取款: %d, 当前余额: %d\n", amount, b.balance)
		b.balance -= amount
		return true
	}
	fmt.Printf("取款失败: %d, 余额不足: %d\n", amount, b.balance)
	return false
}

func (b *BankAccount) GetBalance() int {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.balance
}

func main() {
	account := &BankAccount{balance: 1000}
	
	var wg sync.WaitGroup
	
	// 并发存款
	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			account.Deposit(id * 100)
			time.Sleep(time.Millisecond * 10)
		}(i)
	}
	
	// 并发取款
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			account.Withdraw(id * 200)
			time.Sleep(time.Millisecond * 15)
		}(i)
	}
	
	wg.Wait()
	fmt.Printf("最终余额: %d\n", account.GetBalance())
}

模拟售票

package main

import (
	"fmt"
	"sync"
	"time"
)

type TicketSystem struct {
	availableTickets int
	mu              sync.Mutex
	soldTickets     int
}

func (ts *TicketSystem) BuyTicket(user string) bool {
	ts.mu.Lock()
	defer ts.mu.Unlock()
	
	// 双重检查
	if ts.availableTickets <= 0 {
		fmt.Printf("%s: 票已售完\n", user)
		return false
	}
	
	// 模拟购票处理时间
	time.Sleep(10 * time.Millisecond)
	
	ts.availableTickets--
	ts.soldTickets++
	fmt.Printf("%s: 购票成功,剩余票数: %d\n", user, ts.availableTickets)
	return true
}

func (ts *TicketSystem) GetStats() (int, int) {
	ts.mu.Lock()
	defer ts.mu.Unlock()
	return ts.availableTickets, ts.soldTickets
}

func main() {
	system := &TicketSystem{availableTickets: 100}
	
	var wg sync.WaitGroup
	users := []string{"Alice", "Bob", "Charlie", "David", "Eve"}
	
	// 模拟多个用户同时抢票
	for _, user := range users {
		wg.Add(1)
		go func(name string) {
			defer wg.Done()
			for i := 0; i < 30; i++ { // 每个用户尝试购买30次
				if !system.BuyTicket(name) {
					break
				}
				time.Sleep(time.Millisecond * 5)
			}
		}(user)
	}
	
	wg.Wait()
	
	available, sold := system.GetStats()
	fmt.Printf("\n售票统计:\n")
	fmt.Printf("剩余票数: %d\n", available)
	fmt.Printf("已售票数: %d\n", sold)
}

反射

反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go 可以实现的功能:
1、反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
2、如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法。
3、通过反射,可以修改变量的值,可以调用关联的方法

Go 语言中的变量是分为两部分的:

  • 类型信息:预先定义好的元信息。
  • 值信息:程序运行过程中可动态变化的。

v :=reflect.TypeOf(x) 直接获取对象的类型,要想获取底层原型:v.Kind()

  • v.Kind():获取对象的类型
  • v.Name()获取对象的名称

x := reflect.ValueOf(z) 其中z是指针引用类型,要想修改只能通过v.Elem( ).Kind( )判断类型,然后通过v.Elem( ).SetInt(33)(假设v.Elem( ).Kind( )为int64)对应的类型修改为同一类型。