Goroutine设计原理

为什么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]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值