进程/线程/协程 · 2023年4月5日 0

协程是由应用程序自己管理(而不是内核管理)的线程吗?【】

不是。

用户自己管理的线程叫用户态线程;除了无法利用多核CPU并行外,它和普通线程并无区别——换句话说,一切线程/数据共享方面的理论,对它全部适用。

协程也可以由操作系统实现,只是没有必要而已。比如当年的Windows3.x就是“协作式多进程”——如果把它实现在用户程序里面,那也算是协程。


协程的核心关键词就是“协作”。

所谓“协作”,就是“主动、相互配合”:我不放弃执行权,任何人不能强制我休眠;我做完了自己的工作,那就一定要主动放弃执行权,方便其它人使用CPU工作。

Windows 3.x的“协作式多进程”之所以被后来win95/NT的“抢夺式多进程”代替,是因为进程是不同人开发的,水平良莠不齐,比如事情做完了不知道调用OS提供的接口交出控制权,而是在死循环里傻转,闹得其它所有进程得不到执行机会;甚至可能有人故意搞破坏,比如故意写一个死循环,却不在里面调用yield等可能导致交出执行权的命令。这就破坏了协作的基础。

没错,“协程”是通过yield、await、async等关键字/命令相互协调的(特别的,尤其对Windows 3.x,sleep/select等调用往往也是协作点);除了这些关键字所影响处,你不需要考虑执行权切换问题。

特别注意,千万不要望文生义觉得“协作”就是“可以想办法相互协调”……嗯,其实多线程也能相互协调啊?所以肯定也是协作?

并不是

这里的“协作”指的是“寄希望于主动配合”——你不配合,整个程序(甚至整个操作系统)就卡死了

举例来说,你可以写一个协程,比如标准的range(int start, int end, int step);但在yield return之前故意搞个死循环,不让程序执行要yield处,你就知道厉害了。这就是故意不配合。

因此,当年“协作式多进程”也有人翻译为更贴切、更强调主动配合这个点的“合作式多进程”;可惜后面这个译法最终没能流行起来,大概是“合作”念起来没有“协作”B格充足吧。

为了对付那些不合作的家伙,避免他们只要想搞破坏就能搞死整个系统,这才有了“抢夺式多进程”。

一旦改成“抢夺”之后,时间片就是操作系统安排给每个进程的,用完就必须休眠——你不合作由不得你了。操作系统就把执行权从你手里“抢夺”过来——所以叫“抢夺式多进程”。

借助这个“主动抢夺”,那些不自觉让路的进程就被迫体面了,系统整体稳定性、可用性大幅提升。

但,抢夺式的缺点是:“抢夺”必然会打乱程序执行计划;由于你的执行权随时可能被剥夺,你就不得不动用大量同步手段(锁、信号量等等),以保证数据有效、逻辑有序。这就带来了更大的复杂度和更多不必要的执行现场切换,于是就带来了更大的开发成本和运行期开销。

换句话说,现在,“只在yield等位置切换执行权”的保障没了;你的程序的每一个语句甚至每一个表达式中间的一次运算、甚至一条稍微复杂(非原子性)指令执行到一半,都可能出现执行权交换;所以,你,必须时刻做好防御。稍有不慎,数据的有效性就会遭到破坏。


换一个角度来说,就是:在进程之间,“协作”很难搞起来,甚至完全可以说是个错误——我没打算捣乱,只是技不如人……所以操作系统被搞死这事真不能怪我

因此,对于操作系统,就必须“抢夺式多任务”才足够安全。

但到了进程内部,情况就变了——由于内存默认被线程共享,彼此争抢执行权的线程们就必然产生开发、运行等诸多方面的大量开销,反而使得我们必须付出大量努力才能“正确协作”。

另一方面,同一个程序(进程)肯定是同一个团队甚至同一个人开发的;水平良莠不齐、故意破坏,这些都无从谈起。

那么,不再抢夺执行权、而是“处理告一段落主动交权”(yield等关键字就是用来临时交出执行权的),或者说,“主动合作”而非“被迫合作”——那么数据竞争、执行绪切换等等烧脑/头疼的问题自然就消失了。

于是,程序开发就变得更简洁、执行效率反而更高。

尤其是,如果不需要/没掌握,我们只要简单的不适用线程或者协程就行了,并不会搞死整个程序。

这就是协程流行的根本原因。

至于为什么不让协程也能并行,是因为同时跑在两个核心上的两个协程虽然没有(非主动交接的)执行权切换,但数据竞争不可避免(除非自己保证不使用协程外部的数据),这就把线程的复杂性又找回来了。

打赏 赞(0) 分享'
分享到...
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏