Goroutine 入门

学习

Goroutine 入门

1. 并发

默认给整个应用程序只分配一个逻辑处理器,用于执行所有被创建的 goroutine 如果创建一个 goroutine 并准备运行,就会被放到调度器的全局运行队列中,之后,调度器就将这些队列中的 goroutine 分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列,本地运行队列的 goroutine 会一直等到自己被分配的逻辑处理器执行

2. 运行机制

M2 线程 -> 逻辑处理器 -> goroutine
M3 线程 -> 逻辑处理器 -> goroutine
M2 线程 -> 被阻塞的 goroutine

  1. 当一个 goroutine 被阻塞,线程和 goroutine 会从逻辑处理器分离,线程继续阻塞,等待系统调用返回,逻辑处理器失去了用来运行的线程
  2. 调度器会创建新的线程并将其绑定到逻辑处理器上 调度器会从本地运行队列选择另一个 goroutine 来运行
  3. 一旦被阻塞的系统调用执行完,对应的 goroutine 会放回到本地运行队列,之前的线程会保存好以便继续使用

网络 I/O 调用流程不同
goroutine 会和逻辑处理器分离,并移到集成了网络轮询器的运行,一旦轮询器指示某个网络读或写准备就绪,对应的 goroutine 会重新分配到逻辑处理器上来完成

3. 代码格式

调用 fmt/runtime/sync 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)

wg 用来等待程序完成
var wg sync.WaitGroup wg.Add(2)

函数退出时 Done 通知 main 函数已完成
go func(){ defer wg.Done() }

等待 goroutine 结束
wg.Wait()

创建两个 goroutine 打印前缀
go printPrime("A")

给每个可用的核心分配一个逻辑处理器
runtime.GOMAXPROCS(runtime.NumCPU())

当前 goroutine 从线程退出,并放回到队列
runtime.Gosched()

竞争检测器
go bulid -race ./exmaple

go env set CGO_ENABLED=1

1
2
Final Counter: 2
cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in %PATH%

安装 gcc

4. 问题:竞争状态

对一个共享资源的读写操作必须是原子化的,一个时刻只能有一个 goroutine 对共享资源进行读写操作

goroutine 顺序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
for _, name := range names {
go func() {
    p.Run(&np)
	fmt.Println(1)
	wg.Done()
	fmt.Println(2)
	}()
}
    fmt.Println(3)
	wg.Wait()
	p.Shutdown()
}
1
2
3
4
5
1
2
3
1
2

规范

1
2
3
4
5
for i := 0; i < 5; i++ {
waitGroup.Add(1)
go func() {defer waitGroup.Done()}
}
waitGroup.Wait()

原子函数调用 sync/atomic 和 sync

原子函数以底层的加锁机制来同步访问整型变量和指针
counter 是所有 goroutine 都要增加其值的变量
var(counter int64)
在 for 中添加函数同步整型值,当 goroutine 试图调用任何原子函数时,同一时刻只能有一个 goroutine 完成加法
atomic.AddInt64(&counter, 1)

atomic 中 LoadInt64 用来读取 shutdown 值,StoreInt64 用来修改 shutdown 值,如果哪个 dowork goroutine 试图在 main 函数调用 StoreInt64 的同时调用 LoadInt64 函数,那么原子函数会将这些调用互相同步,保证操作安全不进入竞争状态

互斥锁

mutex 保证同一时间只有一个 goroutine 可以执行临界区代码
var mutex sync.Mutex
mutex.Lock()
mutex.Unlock()

5. 通道

make(chan int)
make(chan string, 10)
向通道发送值或指针用<-
chan <- "str"
从通道接收值
value := <-chan

无缓冲通道要求接收方和发送方的 goroutine 都准备好发送和接受,否则阻塞

有缓冲通道,只有在通道中没有要接收的值时,接收动作才会阻塞,只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞

当通道关闭后,goroutine 依旧可以接收数据,但是不能发送数据

6. 总结

简单阐述了 go 调度器的运行机制,通过原子函数和互斥锁来强制锁住共享资源,解决资源竞争的问题
而通道也可以发送接受共享资源,无缓冲和有缓冲通道的区别在于发送接受方式不同

最后更新于 05月19日 12点44分, 2026年