面试官:谈谈 Go Work Stealing 机制

大家好,我是木川

一、什么是 Work Stealing

Go语言的 Work Stealing 机制是一种用于调度协程(Goroutines)的策略,有助于充分利用多核CPU,提高并发性能,降低锁竞争,从而使Go程序更高效地运行

Work Stealing 机制的核心思想:每个操作系统线程(M)都有一个本地任务队列,它会尽可能地先执行自己队列中的协程。当某个M的P队列为空,而其他P仍有任务时,该M会尝试从其他P中"偷"一些协程来执行,以实现负载均衡

二、Work Stealing 算法

973667daecfbd492aef6df4a11e420ef.png

当从本线程M 从绑定 P 本地 队列、全局G队列、Netpoller 都找不到可执行的 G,会从其它 P 里窃取G并放到当前P上面

  1. 如果全局队列有G,从全局队列窃取的G数量:N = min(len(GRQ)/GOMAXPROCS + 1, len(GRQ/2)) (根据GOMAXPROCS数量负载均衡)

  2. 如果 Netpoller 有G(网络IO被阻塞的G),从Netpoller窃取的G数量:N = 1

  3. 如果从其它P里窃取G,从其它P窃取的G数量:N = len(LRQ)/2(平分负载均衡)

  4. 如果尝试多次一直找不到需要运行的goroutine则进入睡眠状态,等待被其它工作线程唤醒

从其它P窃取G的源码见runtime/proc.go stealWork函数,窃取流程如下:

  1. 选择要窃取的P

  2. 从P中偷走一半G

选择要窃取的P

窃取的实质就是遍历所有P,查看其运行队列是否有goroutine,如果有,则取其一半到当前工作线程的运行队列

为了保证公平性,遍历P时并不是按照数组下标顺序访问P,而是使用了一种伪随机的方式遍历allp中的每个P,防止每次遍历时使用同样的顺序访问allp中的元素

offset := uint32(random()) % nprocs
coprime := 随机选取一个小于nprocs且与nprocs互质的数
const stealTries = 4 // 最多重试4次
for i := 0; i < stealTries; i++ {
  // 随机访问所有 P
	for i := 0; i < nprocs; i++ {
  	  p := allp[offset]
    	从p的运行队列偷取goroutine
	    if 偷取成功 {
        break
   	 }
    	offset += coprime
	    offset = offset % nprocs
	 }
}

可以看到只要随机数不一样,遍历P的顺序也不一样,但可以保证经过nprocs次循环,每个P都会被访问到

从P中偷走一半G

挑选出盗取的对象P之后,则调用 runtime/proc.go 函数runqsteal 盗取P的运行队列中的goroutine,runqsteal函数再调用runqgrap从P的本地队列尾部批量偷走一半的 G

func runqgrab(_p_ *p, batch *[256]guintptr, batchHead uint32, stealRunNextG bool) uint32 {
	for {
		h := atomic.LoadAcq(&_p_.runqhead) // load-acquire, synchronize with other consumers
		t := atomic.LoadAcq(&_p_.runqtail) // load-acquire, synchronize with the producer
		n := t - h        //计算队列中有多少个goroutine
		n = n - n/2     //取队列中goroutine个数的一半
		if n == 0 {
			......
			return ......
		}
		return n
	}
}

三、 Work Stealing 的优点

  1. 提高线程利用率:当线程M绑定的P⽆可运⾏的G时,尝试从其他P偷取G,减少空转

  2. 减少锁竞争:每个M都有自己的本地队列,避免了每次多线程访问全局队列时的锁竞争,提高了性能。

  3. 自动负载均衡:通过偷取其他M的任务,Work Stealing可以自动平衡不同线程的工作负载,提高系统整体的并发性能。

最后给自己的原创 Go 面试小册打个广告,如果你从事 Go 相关开发,欢迎扫码购买,目前 10 元买断,加下面的微信发送支付截图额外赠送一份自己录制的 Go 面试题讲解视频

c6484973e494d7eaa3aada1e673191f1.jpeg

6bf309cc94202d52be3ea317dd4a628f.png

如果对你有帮助,帮我点一下在看或转发,欢迎关注我的公众号

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
工作窃取线程池是一种并行计算框架,它旨在提高多线程任务执行的效率。在传统线程池中,任务通常被划分为多个小任务,由线程池中的线程进行处理。然而,当某些线程已完成其任务,而其他线程仍在处理较大的任务时,就会出现工作不均衡的情况。这种情况下,工作窃取线程池就能发挥作用。 工作窃取线程池的核心思想是将任务分成更小的任务,并将它们放入一个双端队列中,该队列由每个线程私有地维护。每个线程在执行完自己的任务后,会从队列的尾部窃取一个任务进行执行。这样,当某个线程空闲时,它可以从其他线程的队列中窃取任务来执行,以达到任务的平衡分配,提高整体的计算性能。 工作窃取线程池的好处是充分利用线程的空闲时间,减少了线程之间的竞争,提高了线程的利用率,从而提高了整个系统的并发性能。 然而,工作窃取线程池也存在一些问题。首先,任务划分成更小的任务会带来额外的开销,如任务分解和合并的开销。其次,不同线程之间的任务执行顺序可能会受到影响,这可能导致一些任务的执行时间较长。 总的来说,工作窃取线程池是一种优化多线程计算性能的有效方式,它通过平衡任务的分配和提高线程的利用率来提高整体的并发性能。但在使用时,需要考虑任务划分的开销和任务执行顺序的一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值