Go1.14 以下版本 WaitGroup 一个奇怪的问题

polaris · · 2862 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

yifhao 在微信朋友圈晒出这么一道题: 同事定位一个协程池的问题,发现简单的一行不同,却相差这么大。(Go版本1.12、1.13结果类似,Go1.14正常) ```go package main import ( "fmt" "runtime" "sync" "time" ) func main() { wg := sync.WaitGroup{} wg.Add(1000000) // 1)使用这行大约 TotalAlloc = 511 MiB 花费3s for i := 0; i < 1000000; i++ { //wg.Add(1) // 2)使用这行大约 TotalAlloc = 74 MiB 花费0.5s go func() { time.Sleep(time.Duration(1) * time.Millisecond) wg.Done() }() } wg.Wait() mem := runtime.MemStats{} runtime.ReadMemStats(&mem) curMem := mem.TotalAlloc / 1024 / 1024 fmt.Printf("\tTotalAlloc = %v MiB \n", curMem) } ``` 解释: 很多时候查问题就像是做一道智力题,表象是这样,然而实际原因和表象却不相干。 表象是waitgroup问题,然而从waitgroup原理和并发冲突去理解的都是歧途,一开始我也是这么想的. 其实是go1.14之前的tight loop不能被gc中断导致的问题. 大概生成上千次个协程时, 这时候内存到了gc阈值, sleep执行时会分配timer, 这时知道要gc了,告诉所有协程和P, 现在要gc了, 你们要停止.加了wg.Add(1)的那个, 正好Add方法有点复杂, 调用前有抢占点检查, 所以主协程可以被gc中断,gc得以执行完,而sleep完后的协程可以被复用. 而先Add(1000000)的那个, 主协程的for循环中没有检查自身被中断的地方. 虽然主协程也能在分配g时得知要gc了, 但是创建一个协程是由g0和systemstack执行, 这里面不能发起gc, 就返回继续执行. 所以主流程协程一直在运行. 调度timer的线程需要P才能恢复sleep的协程, 所有的P都处于gc暂停态, 使得sleep的协程也不能恢复并运行完,也就不能被复用. 主流程就一直分配新的协程, 之前那些sleep的协程不能被复用, 内存和CPU都增加很多.

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2862 次点击  ∙  1 赞  
加入收藏 微博
4 回复  |  直到 2020-04-10 21:45:43
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传