golang并发编程-03-协程(Goroutine)概述
1. 协程(Goroutine)
Goroutine——协程,有的人跟我说不是,我不想反驳,也不打算改。
1.1 一些相关概念
下边这些概念之前提到过,这里需要用到,所以再诠释一遍。
1)线程的三个核心元素:
- M:Machine的缩写。一个M代表了一个内核线程。
- P:Processor的缩写。一个P代表了M所需的上下文环境。
- G:Goroutine的缩写。一个G代表了对一段需要被并发执行的Go语言代码的封装。
2)KSE
- 内核调度实体
- 在一个M的生命周期内,它会且仅会与一个KSE产生关联。
1.2 协程的使用
之前在 《go语言基础-07-协程-01-并发》里边讲过协程的基本使用。可以回翻。
1.3 一个示例
1)用协程打印结果
func main() { names := []string{"关羽", "张飞", "赵云", "马超","黄忠"} for _,name := range names { go fmt.Printf("Hello %s.\n", name) time.Sleep(5 * time.Second) runtime.Gosched() }
说明:
如果不加sleep,协程们还没有打印完,主线程就结束了,结果打出几条得看命。
当然也可以用 waitgroup 我们后边会讲到
2)换成协程调用函数
func main() { names := []string{"关羽", "张飞", "赵云", "马超","黄忠"} for _,name := range names { go func() { fmt.Printf("Hello %s.\n", name) }() } time.Sleep(5 * time.Second) runtime.Gosched() }
和上边的区别是协程调用了一个函数,你猜打印结果是什么?
答案是:没准!
解释:协程调用的函数启动时,主线程的for循环跑到哪儿就接收哪个变量。
Hello 赵云. Hello 黄忠. Hello 黄忠. Hello 黄忠. Hello 赵云.
3)用sleep拉开时间
这种方法是我瞎说的,虽然结果对,但是没这么干的,还不如不用协程。
func main() { names := []string{"关羽", "张飞", "赵云", "马超","黄忠"} for _,name := range names { go func() { fmt.Printf("Hello %s.\n", name) }() time.Sleep(1 * time.Second) } runtime.Gosched() }
4)正确写法
for循环里设置一个变量tmp接收name值,变量值不会传到本次循环外,因此保证了协程的正确打印。
func main() { names := []string{"关羽", "张飞", "赵云", "马超","黄忠"} for _,name := range names { tmp := name go func(tmp string) { fmt.Printf("Hello %s.\n", tmp) }(tmp) } time.Sleep(3*time.Second) runtime.Gosched() }
输出
Hello 关羽. Hello 马超. Hello 赵云. Hello 张飞. Hello 黄忠.
但是多打印几次,你会发现顺序是乱的。
按顺序打印我们会在channel 一章里边说。
2. runtime包
2.1 GOMAXPROCS函数
- 作用:应用程序可以在运行期间设置运行时系统中的P的最大数量。
- 范围:1~256的范围内
- 应用:
建议尽早调用(main函数开始时或init函数中)
程序可以在运行期间设置运行时系统中的P的最大数量,但由于这样做会引起“Stop the world”。
2.2 Goexit函数
- 作用:
该函数被调用,会立即使调用它的Goroutine的运行终止。
但该Goroutine中未执行的defer语句会被执行。
该函数会把被终止运行的Goroutine置于Gdead状态,并将其放入调度器的自由G列表。这样,调度器可以在有需要时重新启用此Goroutine。最后,应用程序对runtime.Goexit函数的调用还会触发调度器的一轮调度流程。
2.3 Gosched函数
- 作用:暂停调用它的Goroutine的运行。
调用它的Goroutine会被重新置于Grunnable状态,并被放入到调度器的可运行G队列中。这也是使用“暂停”这个词的原因。因为经过调度器的调度,该Goroutine不久就会再次被运行。这样做完全是为了让其他Goroutine立即有被运行的机会。
2.4 NumGoroutine函数
- 作用:返回运行时系统中的处于特定状态的Goroutine的数量
- 特定状态:
Grunnable、Grunning、Gsyscall、Gwaiting
2.5 LockOSThread / UnlockOSThread函数
- LockOSThread:将Goroutine与当前运行它的M锁定在一起
- UnlockOSThread:解锁Goroutine与当前运行它的M
2.6 runtime/debug.SetMaxStack函数
- 作用:约束单个Goroutine所能申请的栈空间的最大尺寸
- 使用:在main函数以及main包的init函数真正被执行之前,主Goroutine会对此进行默认的设置。
2.7 runtime/debug.SetMaxThreads函数
- 作用:设置运行时系统所使用的内核线程的数量
2.8 与垃圾回收相关函数
- runtime.GC
运行时系统进行一次强制性的垃圾收集。 - runtime/debug.SetGCPercent
设置垃圾收集比率以进行垃圾收集。
具体计算公式如下:
<触发垃圾收集的堆内存单元增量> = <上一次垃圾收集完成后的堆内存单元数量> * (<垃圾收集比率> / 100)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了