加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

02.2跟雨痕看go源码- 并发清理与三色标记

发布时间:2020-12-16 18:28:05 所属栏目:大数据 来源:网络整理
导读:据说这是go优化最狠的地方。 http://www.jb51.cc/article/p-gcxizrkd-beo.html 大意是说twitch.tv觉得一次标记的STW(stop the world,就是jojo里面的技能,标记和回收时间很长,所有应用代码都无法工作)太强力了竟然需要2秒钟,不能忍,所以在和go开发团队

据说这是go优化最狠的地方。
http://www.52php.cn/article/p-gcxizrkd-beo.html
大意是说twitch.tv觉得一次标记的STW(stop the world,就是jojo里面的技能,标记和回收时间很长,所有应用代码都无法工作)太强力了竟然需要2秒钟,不能忍,所以在和go开发团队多次沟通后,开始了大量的优化,这里的代码充斥着并发优化的痕迹。据说到了go1.7的时候STW仅剩下1毫秒。。。go1.5就已经达到了200ms(已经不错了哦)。
我们现在可以看一下go1.5所做的优化orz。大神好厉害。orz orz orz。

三色标记的基本逻辑:
- 起初所有对象都是白色。
- 扫描找出所有可达对象标记为灰色,翻入待处理队列。
- 从队列中提取灰色对象,将其引用的对象标记为灰色放入队列。自身标记为黑色。
- 写屏障件事所有对象内存修改,重新标色或放回队列。

1.初始化
在schedinit里面的gcinit()初始化
启动,在申请新内存时的函数newobject()的末尾就会检查一下,如果达到一定的量就触发。
最终调用gc函数启动。

首先先来看雨痕大大给的gc的标记清理的主流程:

这里的主要牛b的代码在并发扫描和并发清理:

2.并发扫描
第一次
gcBgMarkStartWorkers,为每个p绑定一个worker,每个p的worker会消耗gcw里面的任务进行循环标记,它自己还有好多模式,专门标记模式和普通模式(本质上调用了gcDrain/gcDrainUntilPreempt消费 gcw这个队列)。
初始化喂这些gcw是一个叫gcscan_m (通过parfosetup&parfordo框架,虽然这里就用了一个线程,好惨,工作是_RootCount + local_allglen的G的引用)调用的markroot->scang->scanblock->扫描所有的灰色对象喂入第一批的灰色任务到gcw。。。

第二次 直接调用markroot(nil,_RootData) markroot(nil,_RootBss),对第一次没处理干净的新增数据(进入写屏障的数据)进行仔细清理。
用第一次没有关闭的gcBgMarkStartWorkers进行消费。。但第二次结束后手动调用atomicstore(&gcBlackenEnabled,0)禁用了markWorker的工作(schedule判断gcBlackenEnabled失效,不再唤醒markWorker了)。

第三次标记是在STW里面的,gcMark通过parforsetup(work.markfor,work.nproc,uint32(_RootCount+allglen),false,markroot)注册多个线程任务,
然后通过helpgc设定标志位->触发STW停止在stopm的代码,使它们(因为一般是多个M)进入gchelper()->并发调用parfordo(即work.markfor函数)并启动gcDrain() 完成后回调 work.alldone,使得主线程能够做收尾工作。

3.并发清理
如果是同步清理那直接调用sweepone->依次遍历找出所有的work的spans(work.spans),然后,逐个回收spans(回收部分在02.01提到了)。
如果是并发清理则每调用一次gosweepone就重新gosched一次避免无限堵塞(这里是异步的,直接启动后就return 了),如果是同步清理则会一个扫到结束再return。
无论如何申请新的内存在mcentral阶段也会考虑帮着sweepone一下。

4.监控
在主要启动的main函数里面有
newm(sysmon,nil)这么个操作。就是在启动用户的main函数之前,默默的启动这个监控程序,其中的1.每两分钟强制出发回收操作,2.垃圾达到一定限度强制回收操作。

5.parfosetup&parfordo并行框架
parforsetup就是进行任务分组的通过一个for循环,为所有的任务块均匀地按个数分组(数据倾斜只能靠运行时的偷了)。
parfordo 则是以完成本职工作为主,完成后会去看一下是否大家都做完了如果是则结束(通过一个xadd(&desc.done,1)这种信号量去加减并判断),如果不是则尝试偷4次,偷则偷一半的任务,继续工作。。。多次没有偷到也放弃。

6.缓存队列 这个东西就是一个cas的缓存而已,有一堆empty,partial,full然后,从full取,给empty存。。empty存满了变成full,full取完了变成empty。通过cas(并且加入一个递增的参数来一起做cas判断来绕过ABA问题,编码时类似于编成了A1 B2 A3,cas能够区分错误)。具体这块雨痕写的很简略,实际上有很多代码的。。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读