您的位置:首页 > 编程语言 > Go语言

Goroutine设计原理

2017-01-07 00:57 148 查看

为什么Golang需要单独开发一个Goroutine?

开销问题:

POSIX的thread API虽然能够提供丰富的API,例如配置自己的CPU亲和性,申请资源等等,线程在得到了很多与进程相同的控制权的同时,开销也非常的大,在Goroutine中则不需这些额外的开销,所以一个Golang的程序中可以支持10w级别的Goroutine。

调度性能:

在Golang的程序中,操作系统级别的线程调度,通常不会做出合适的调度决策。例如在GC时,内存必须要达到一个一致的状态。在Goroutine机制里,Golang可以控制Goroutine的调度,从而在一个合适的时间进行GC。

Goroutine的实现原理

两种备选方案

(M:1)多个用户态的线程对应一个系统线程,它可以做快速的上下文切换。缺点是不能有效利用多核CPU

(1:1)一个用户态的线程对应一个系统线程,它可以利用多核机制,但上下文切换需要消耗额外的资源

Golang的做法

(M:N)Golang采取了一种多对多的方案。M个用户线程对应N个系统线程,缺点增加了调度器的实现难度

角色:

M: 代表了系统线程,由操作系统管理

G:Goroutine的实体,包括了调用栈,重要的调度信息,例如channel等。

P:衔接M和G的调度上下文,它负责将等待执行的G与M对接。

P的数量由环境变量中的
GOMAXPROCS
决定,通常来说它是和核心数对应,例如在4Core的服务器上回启动4个线程。G会有很多个,每个P会将Goroutine从一个就绪的队列中做Pop操作,为了减小锁的竞争,通常情况下每个P会负责一个队列。

挂起

在Goroutine需要执行一个系统调用时,由于M是一个线程,所以必须等待它执行完才能执行其他的Goroutine。当一个新的Goroutine产生,M需要保证会有另外的一个M能够执行这个G,简单来说,当一个M进行系统调用,需要保证有另外的一个M能够继续执行Go代码。

何时恢复?

当系统调用返回时,M需要找到一个对应的P,以便能够运行Goroutine,它首先会尝试从其他线程中窃取一个P,如果不成功,它会将Goroutine放在一个全局的队列中,并将自己放在thread cache中。

如何窃取

这里有篇paper来描述这个设计:work-steal.

简单来说,当队列不平衡时,会从其他队列中截取一部分Goroutine到P上进行调度。

参考链接

https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit#

https://morsmachine.dk/go-scheduler

(翻译)[]https://www.zhihu.com/question/20862617]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Golang