什么是协程(不严谨解释)
作者:阿猫
没有啥复杂的东西,考虑清楚需求,就可以很自然的衍生出这些解决方案。
- 一开始大家想要同一时间执行那么三五个程序,大家能一块跑一跑。特别是UI什么的,别一上计算量比较大的玩意就跟死机一样。于是就有了并发,从程序员的角度可以看成是多个独立的逻辑流。内部可以是多cpu并行,也可以是单cpu时间分片,能快速的切换逻辑流,看起来像是大家一块跑的就行。
- 但是一块跑就有问题了。我计算到一半,刚把多次方程解到最后一步,你突然插进来,我的中间状态咋办,我用来储存的内存被你覆盖了咋办?所以跑在一个cpu里面的并发都需要处理上下文切换的问题。进程就是这样抽象出来个一个概念,搭配虚拟内存、进程表之类的东西,用来管理独立的程序运行、切换。
- 后来一电脑上有了好几个cpu,好咧,大家都别闲着,一人跑一进程。就是所谓的并行。
- 因为程序的使用涉及大量的计算机资源配置,把这活随意的交给用户程序,非常容易让整个系统分分钟被搞跪,资源分配也很难做到相对的公平。所以核心的操作需要陷入内核(kernel),切换到操作系统,让老大帮你来做。
- 有的时候碰着I/O访问,阻塞了后面所有的计算。空着也是空着,老大就直接把CPU切换到其他进程,让人家先用着。当然除了I\O阻塞,还有时钟阻塞等等。一开始大家都这样弄,后来发现不成,太慢了。为啥呀,一切换进程得反复进入内核,置换掉一大堆状态。进程数一高,大部分系统资源就被进程切换给吃掉了。后来搞出线程的概念,大致意思就是,这个地方阻塞了,但我还有其他地方的逻辑流可以计算,这些逻辑流是共享一个地址空间的,不用特别麻烦的切换页表、刷新TLB,只要把寄存器刷新一遍就行,能比切换进程开销少点。
- 如果连时钟阻塞、 线程切换这些功能我们都不需要了,自己在进程里面写一个逻辑流调度的东西。那么我们即可以利用到并发优势,又可以避免反复系统调用,还有进程切换造成的开销,分分钟给你上几千个逻辑流不费力。这就是用户态线程。
- 从上面可以看到,实现一个用户态线程有两个必须要处理的问题:一是碰着阻塞式I\O会导致整个进程被挂起;二是由于缺乏时钟阻塞,进程需要自己拥有调度线程的能力。如果一种实现使得每个线程需要自己通过调用某个方法,主动交出控制权。那么我们就称这种用户态线程是协作式的,即是协程。
在用户态为每个协程维护调用上下文,以前都是内核帮你维护了,现在要用户态自己来,好处呢就是自己可以控制协程的状态,start, stop, 切换到另外一个协程
作用
协程在io操作比较多,且IO是瓶颈的地方才有用。
首先协程算是用户态的线程,优势主要是少了内核态用户态的切换和能自己来做调度。
然后协程一般只在有IO操作的时候才能用到,对于一些会阻塞的IO操作,可以自己选择协程切换,等IO就绪了再切回来,可以更充分利用CPU。
就像你在做菜,切了萝卜去煲汤,另外还要煮鸡蛋:
1.线程的话,就是你切萝卜到一半就换成煮鸡蛋,然后一会换回来继续切萝卜(线程什么时候切换是操作系统决定的)
2.协程的话,就是你切完萝卜开始煲汤,然后自己换出去切煮鸡蛋(因为煲汤不用一直卡在那里嘛)
应用到实际你的例子,就是爬虫做网络请求的时候可以切出去,数据库也一样。
如果单纯是运算没有IO,那么协程就没什么优势了
作者:mayf3
讨论
答:协程最大的好处在于其代价低廉,一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
质疑:无法利用多核优势,协程本质还是单线程,不知你的高并发是怎么得出来的结论
答:
一般用协程来解决CPU负载的时候,都是多进程worker,然后单个worker用一个单线程实现尽可能多的协程(再复杂一点还可以做进程到CPU的绑定)。所以,多核的利用其实不是问题。
然后“协程解决高并发”的问题背景一般都是:IO并发很高(那么,很自然地线程数也要调高),同时内核调度和CPU都比较弱鸡,大量的CPU时间浪费在“无效的context switch”上面。注意是“无效的context switch”,如果这个背景不成立的话,协程是优化不了多少CPU负载的;
因为它挂载在进程下面,本身开销小,切换开销极小(作为userspace thread存在),同时是单线程意味着无需锁,可以承载大量的并发,逐条执行。