Go-Goroutine 基础与调度机制

Go-Goroutine 基础与调度机制
怀光1. 什么是 Goroutine
Goroutine
是 Go 语言中的一种轻量级线程实现。相比传统线程,Goroutine
的开销更小,因此可以在同一进程内运行成千上万个 Goroutine
,使得 Go 能够更高效地处理并发任务。
在 Go 中,Goroutine
以独立的协程形式执行,通过 go
关键字启动。所有的 Goroutine
在同一地址空间中运行,可以直接共享数据,但需要小心并发冲突。
2. Goroutine 的基本用法
要启动一个 Goroutine
,我们只需在函数调用前添加 go
关键字:
1 | package main |
在上述代码中,我们通过 go sayHello()
启动了一个新的 Goroutine
。注意,main
函数中的 fmt.Println("Hello from main")
和 sayHello
函数是并发执行的。为了避免 main
函数过早退出,我们使用 time.Sleep
暂停一秒,以便 sayHello
有机会运行。
使用匿名函数启动 Goroutine
我们也可以直接在 Goroutine
中使用匿名函数:
1 | go func() { |
这种方式特别适合一些一次性任务或轻量任务的并发执行。
3. Goroutine 的调度机制
Go 语言中有一个 GMP
模型来调度 Goroutine
,其中:
G
代表Goroutine
M
代表Machine
,对应一个内核线程P
代表Processor
,用于管理G
和M
的绑定
GMP 模型简介
G
(Goroutine):代表需要执行的任务,包含了任务函数和运行时栈。M
(Machine):代表系统的内核线程,负责实际的执行。P
(Processor):代表逻辑处理器,负责调度Goroutine
。
P
通过调度 Goroutine
队列,分发任务给 M
去执行。G
绑定 P
后再由 M
执行,如果 G
阻塞,M
可以切换去执行其他 Goroutine
,从而达到非阻塞的效果。
Goroutine 的协作式调度
Go 的调度器采用协作式调度,Goroutine
在适当时刻会主动让出 CPU,调度器则把控制权交给下一个 Goroutine
。例如,在 Goroutine
中使用 time.Sleep
或 runtime.Gosched()
会主动触发调度,避免长时间占用 CPU。
1 | package main |
在这里,我们使用 runtime.Gosched()
函数让出 CPU,让调度器去执行其他的 Goroutine
。
4. 实际应用示例
并发任务的执行
在很多场景下,Goroutine 可以帮助我们执行多个并发任务,比如以下例子中启动了多个 Goroutine 来执行计算:
1 | package main |
在这个例子中,我们使用 sync.WaitGroup
等待所有 Goroutine
完成,确保在 main
函数结束之前所有任务都完成。
5. 总结与最佳实践
- 不要阻塞
Goroutine
:在Goroutine
中避免使用阻塞的操作,可以使用通道(Channel
)来协调并发任务。 - 合理使用
WaitGroup
:在并发任务中,sync.WaitGroup
可以帮助我们控制Goroutine
的结束,避免main
函数过早退出。 - 关注内存泄漏:如果创建了大量的
Goroutine
,一定要小心避免内存泄漏,尽量确保所有Goroutine
都能正确结束。 - 熟悉 GMP 模型:理解 Go 的调度机制和 GMP 模型有助于编写高效的并发程序。
- 不要依赖调度顺序:
Goroutine
的调度顺序是不可控的,编写并发程序时要确保结果不依赖于执行的顺序。