[编程技术] 一文看懂Go语言协程的设计与原理
作者:CC下载站 日期:2021-04-18 13:00:51 浏览:10 分类:编程开发
背景
Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:main Goroutine。Go 程序从 main 包的 main() 函数开始,在程序启动时,Go 程序就会为 main() 函数创建一个默认的 goroutine。
为了了解Go语言协程的设计,我们从历史设计出发,来看看最终Goroutine怎么一步一步到现在的设计的。
单进程时代
早期的操作系统每个程序就是一个进程,操作系统在一段时间只能运行一个进程,直到这个进程运行完,才能运行下一个进程,这个时期可以成为单进程时代——串行时代。
如图:进程之间串行执行,A、B、C 三个进程按顺序执行。
单进程时代的两个问题:
- 单一执行流程、计算机只能一个任务一个任务的处理。
- 进程阻塞所带来的CPU浪费时间是不可避免的(如进程A阻塞了,然后CPU是单进程没有任何的切换能力,但是需要等待进程A结束后才能执行下个进程)
遇到这种问题,我们怎么才能充分利用CPU呢?
多进程时代
后来操作系统就具有了最早的并发能力:多进程并发,当一个进程阻塞的时候,切换到另外等待执行的进程,这样就能尽量把CPU利用起来,CPU就不浪费了。
在多进程时代,有了时间片的概念,进程按照调度算法分时间片在 CPU 上执行,A、B、C 三个进程按照时间片并发执行。(调度算法)
这样做有两个优点:
- 对于单个核可以并发执行多个进程,应用场景更加丰富,
- 当某个进程 IO 阻塞时,也能保证 CPU 的利用率。
但是随着时代的发展,CPU 通过进程来进行调度的缺点也越发的明显。
进程切换需要:
- 切换页目录以使用新的地址空间
- 切换内核栈和硬件上下文
因为进程拥有太多资源,在创建、切换和销毁的时候,都会占用很长的时间,CPU虽然利用起来了,但CPU有很大的一部分都被用来进行进程调度了。
怎么才能提高CPU的利用率呢?
多线程时代
所以,轻量级的进程:线程诞生了。线程运行所需要的资源比进程少多了。
对于线程和进程,我们可以这么理解:
- 当进程只有一个线程时,可以认为进程就等于线程。
- 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
- 线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
线程是CPU调度的最小单位, 进程是资源分配的最小单位。
- 进程:进程是资源分配的最小单位,进程在执行过程中拥有独立的内存单元。
- 线程:线程是CPU调度的最小单位,线程切换只须保存和设置少量寄存器的内容。
虽然线程比较轻量,但是在调度时也有比较大的额外开销。每个线程会都占用 1M 以上的内存空间,在切换线程时不止会消耗较多的内存,恢复寄存器中的内容还需要向操作系统申请或者销毁资源。
多进程、多线程已经提高了系统的并发能力,但是在当今互联网高并发场景下,为每个任务都创建一个线程是不现实的,因为每个线程需要有自己的栈空间,大量的线程需要占用大量的内存空间,同时线程的数量还受系统参数threads-max
等参数的限制。
有没有更轻量级的线程来支持当今互联网的高并发场景呢。如何才能充分利用CPU、内存等资源的情况下,实现更高的并发?
协程时代
协程作为用户态线程,也是轻量级的线程,用来解决高并发场景下线程切换的资源开销。
你可能知道:线程分为内核态线程和用户态线程,用户态线程需要绑定内核态线程,CPU并不能感知用户态线程的存在,它只知道它在运行1个线程,这个线程实际是内核态线程。
用户态线程实际有个名字叫协程(co-routine),为了容易区分,我们使用协程指用户态线程,使用线程指内核态线程。
协程跟线程是有区别的
- 线程/进程是内核进行调度,有 CPU 时间片的概念,进行 抢占式调度(有多种调度算法)
- 协程 对内核是透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的 CPU 控制权切换到其他进程/线程,通常只能进行 协作式调度,需要协程自己主动把控制权转让出去之后,其他协程才能被执行到。
协程的调度
1:1 调度
1个协程绑定1个线程,这种最容易实现。协程的调度都由CPU完成了,但有一个缺点是协程的创建、删除和切换的代价都由CPU完成,上下文切换很慢,同等于线程切换。
N:1 调度
N个协程绑定1个线程,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速。但也有很大的缺点,1个进程的所有协程都绑定在1个线程上,一是某个程序用不了硬件的多核加速能力,二是一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,根本就没有并发的能力了。
M:N 调度
M个协程绑定N个线程,是N:1和1:1类型的结合,克服了以上2种模型的缺点,但实现起来最为复杂。
go语言协程调度
Go runtime的调度器
Go 语言的调度器通过使用与 CPU 数量相等的线程减少线程频繁切换的内存开销,同时在每一个线程上执行额外开销更低的 Goroutine 来降低操作系统和硬件的负载。
而每个goroutine非常轻量级,只占几KB的内存,这就能在有限的内存空间内支持大量goroutine,支持了更多的并发。虽然一个goroutine的栈只占几KB,但实际是可伸缩的,如果需要更多内容,runtime会自动为goroutine分配。
goroutine建立在操作系统线程基础之上,它与操作系统线程之间实现了一个多对多(M:N)的两级线程模型。
这里的 M:N 是指M个goroutine运行在N个内核线程之上,内核负责对这N个操作系统线程进行调度,而这N个系统线程又通过goroutine调度器负责对这M个goroutine进行调度和运行。
G-M模型
Go1.0的协程是G-M模型
- G指Goroutine,本质上是轻量级线程,包括了调用栈,重要的调度信息,例如channel
- M指Machine,一个M关联一个内核OS线程,由操作系统管理。
M(内核线程)
想要执行、放回G都必须访问全局G队列
,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。
这个调度器有几个缺点:
- 存在单一全局互斥锁和集中状态。全局锁保护所有 goroutine 相关操作(如:创建、完成、重新调度等),导致锁竞争问题严重;
- goroutine 传递问题:经常在 M 之间传递“可运行”的 goroutine,这导致调度延迟增大;
- 每个线程 M 都需要做内存缓存(M.mcache),导致内存占用过高,且数据局部性较差;
- 系统调用频繁地阻塞和解除阻塞正在运行的线程,导致额外的性能损耗。
G-P-M模型
新的协程调度器引入了P(Processor),成为了完善的GPM模型。Processor,它包含了运行goroutine的资源,如果线程想运行goroutine,必须先获取P,P中还包含了可运行的G队列。
上图中各个模块的作用如下:
- 全局队列:存放等待运行G
- P的本地队列:和全局队列类似,存放的也是等待运行的G,存放数量上限256个。新建G时,
G优先加入到P的本地队列,如果队列满了,则会把本地队列中的一半G移动到全局队列
- P列表:所有的P都在程序启动时创建,保存在数组中,最多有GOMAXPROCS个,可通过runtime.GOMAXPROCS(N)修改,N表示设置的个数。
- M:每个M代表一个内核线程,操作系统调度器负责把内核线程分配到CPU的核心上执行。
简单的来说,一个G的执行需要M和P的支持。一个M在与一个P关联之后形成了一个有效的G运行环境【内核线程 + 上下文环境】。每个P都会包含一个可运行的G的队列 (runq )。
go func
都代表一个G,无限制
代表一个用户代码执行流
P Processor
runtime.p 运行G的上下文,调度器,包括mcache,runq和free g等。
默认为机器核数,可通过 GOMAXPROCS
环境变量调整。
表示执行所需的资源
M Machine
runtime.m 对应一个内核线程
比P多,M的最大数量可以进行设置,这个初始值是10000
代表执行者,底层线程
调度策略: 调度器核心思想是尽可能避免频繁的创建、销毁线程,对线程进行复用以提高效率。
- work stealing机制(窃取式)
当本线程无G可运行时,从其他线程绑定的P窃取G,而不是直接销毁线程。
- hand off机制
当本线程M1因为G进行的系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的M0执行。
抢占
一个goroutine最多占用CPU 10ms,防止其他goroutine等待太久得不到执行被“饿死”。
全局G队列
全局G队列是有互斥锁保护的,访问需要竞争锁,新的调度器将其功能弱化了,当M执行work stealing从其他P窃取不到G时,才会去全局G队列获取G。
总结
本文只是从历史和宏观角度解释了Goroutine的设计原理,当然具体的代码实现远比这个复杂。后续会继续更新Go语言协程的源码到底是怎么实现的。
P的作用是什么?
对比我们可以发现:
- G-M模型 起初的Go并发性能并不十分亮眼,协程和系统线程的调度比较粗暴,导致很多性能问题,如全局资源锁、M的内存过高等造成许多性能损耗。
- G-P-M模型 加入P的设计之后实现了一个通过 work stealing 的调度算法:由P来维护Goroutine队列并选择一个适当的M绑定。P提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P的数量决定了系统内最大可并行的G的数量。
goroutine存在的意义是什么?
goroutine 的存在必然是为了换个方式解决操作系统线程的一些弊端
- 创建和切换太重 操作系统线程的创建和切换都需要进入内核,而进入内核所消耗的性能代价比较高,开销较大;
- 内存使用太重 内核在创建操作系统线程时默认会为其分配一个较大的栈内存,内核在创建操作系统线程时默认会为其分配一个较大的栈内存,同时会有溢出的风险;
goroutine的优势也就是 开销小
- goroutine是用户态线程,其创建和切换都在用户代码中完成而无需进入操作系统内核,所以其开销要远远小于系统线程的创建和切换;
- goroutine启动时默认栈大小只有2k,这在多数情况下已经够用了,即使不够用,goroutine的栈也会自动扩大,同时,如果栈太大了过于浪费它还能自动收缩,这样既没有栈溢出的风险,也不会造成栈内存空间的大量浪费。
其他语言的协程
协程是个好东西,不少语言支持了协程,比如:Lua、Erlang,就算语言不支持,也有库支持协程,比如C语言的coroutine、Java的coroutines、C++的libco和libgo、Kotlin的kotlinx.coroutines、Python的gevent。
<全文完>
- 上一篇:[美图] 坂井泉水修复图
- 下一篇:[网文资讯] 新华社批音乐App诱导重复消费
猜你还喜欢
- 03-29 [编程相关] Winform窗体圆角以及描边完美解决方案
- 03-29 [前端问题] has been blocked by CORS policy跨域问题解决
- 03-29 [编程相关] GitHub Actions 入门教程
- 03-29 [编程探讨] CSS Grid 网格布局教程
- 10-12 [编程相关] python实现文件夹所有文件编码从GBK转为UTF8
- 10-11 [编程算法] opencv之霍夫变换:圆
- 10-11 [编程算法] OpenCV Camshift算法+目标跟踪源码
- 10-11 [Python] python 创建 Telnet 客户端
- 10-11 [编程相关] Python 基于 Yolov8 + CPU 实现物体检测
- 03-15 [脚本工具] 使用go语言开发自动化脚本 - 一键定场、抢购、预约、捡漏
- 01-08 [编程技术] 秒杀面试官系列 - Redis zset底层是怎么实现的
- 01-05 [编程技术] 《Redis设计与实现》pdf
取消回复欢迎 你 发表评论:
- 精品推荐!
-
- 最新文章
- 热门文章
- 热评文章
[课程] 轻松汽车摄影-全能课,最全汽车视频拍摄技巧(32节课)
[资料教程] 辣条制作配方及教程
[音乐] 低音炮车载340首
[课程] 夜鹰10分钟救命课(完结)
[资料] 寻道网道医精品视频课程集合15部
[资料课程] 适合3-7岁学前幼小衔接 《22天搞定拼音》,认、读、拼、写
[课程] 衣之镖-- 《辅行诀五脏用药法要》研究 线上54讲课程视频
[课程] 12节在家也能练出性感蜜桃臀,让臀部变得圆、紧、翘
[游戏娱乐] 《灵魂石幸存者》v10g中文版
[跨境电商] TikTok中视频课程30天线上陪跑
[资料] [大学期末救急课] 猴博士+高斯课堂+斐多课堂,全集视频合集
[云资源] 价值2万元的老男孩Python教程
[书库] 史上最全摄影书推荐(附700本PDF版打包下载)
[云资源] 花了一千多元买的私人健身教程
[下载工具] Internet Download Manager 6.42.7 (IDM)
[影视] 灌篮高手 WEB-DL版下载/Slam Dunk/スラムダンク/灌篮高手:THE FIRST/灌篮高手电影版 2022 The First Slam Dunk 61.35G
[安卓软件] 酷我音乐APP_v10.7.6.4 去广告破解豪华VIP版
[即时通讯] 微信PC版WeChat 3.9.9.43 多开防撤回绿色版
[安卓软件] Solid Explorer文件管理器APP 2.8.38 破解版
[浏览器] Google Chrome v123.0.6312.59便携增强版
[云资源] 价值2万元的老男孩Python教程
[影视] 灌篮高手 WEB-DL版下载/Slam Dunk/スラムダンク/灌篮高手:THE FIRST/灌篮高手电影版 2022 The First Slam Dunk 61.35G
[云资源] 花了一千多元买的私人健身教程
[书库] 史上最全摄影书推荐(附700本PDF版打包下载)
[动画] 北斗神拳(1984) [两季合集] [MKV]
[资料] 抗战阵亡将士资料+续编
[电视剧] 三体 (2024) 全8集 网飞版本 中文字幕 合集
[纪录片] 河西走廊【10集 国语 中文字幕 1080P 10.8G MP4】
[电影] 2024年喜剧片·热辣滚烫 [mp4]
[影视] 铁爪 WEB-DL版下载 2023 The Iron Claw 23.48G
- 最新评论
-
杂物房内的旧档资源不保证有效CC下载站 评论于:05-14 不能**123 评论于:05-14 我想看看mw2ddyy 评论于:04-26 好东西阿zfy123123 评论于:04-18 谢谢楼主xiaoqi 评论于:04-12 勿在线解压,勿手机解压,请在电脑上用最新款压缩软件解压!推荐360压缩或者好压CC下载站 评论于:04-10 无法解压啊,客服能不能给个解压教程ravengrey 评论于:04-10 谢谢支持!!CC下载站 评论于:03-26 很棒的资源,感谢分享云体风身 评论于:03-26 感谢分享,好东西云体风身 评论于:03-26
- 热门tag