如果你对以下几个问题有疑问,那么本文可能会有所帮助。
1.2.3 谈协程绕不开线程,按传统还得从进程谈起,不过我想业内人员对进程和线程应该是耳熟能详,这里就简单概括下。 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度;线程拥有自己独立的栈,共享堆(也可以有自己的私有域),不共享栈,线程亦由操作系统调度。一个进程可以有多个线程。 多线程一直以来是面试必考点,虽然[web]服务端开发人员似乎从来不用直接操作线程,其实是因为框架帮忙维护了,开发人员只需要关心业务实现。这也导致了部分人对多线程的某些概念模糊不清。比如关于多线程的效率:在多核cpu下,多个线程可以并行运行在不同内核上,效率高;而在单核cpu中,多个线程的并行执行其实是一个错觉,因为它们都是运行在一个内核上,一个cpu内核同一时间只能执行一个进程/线程,因此在一个内核上的多线程执行其实效率反而比串行执行低,只是给用户一种并发的错觉,反而增加了线程切换的时间。 但是效率的高低还要看线程占用cpu资源的占用率,比如存在大量IO操作,IO比较慢。也就是说,如果只有单线程,那么一旦涉及到IO操作,线程可能会被阻塞,程序的其余逻辑就只能傻等,就算那些逻辑不依赖于这个IO操作,此时线程对CPU的使用为0,CPU就是空闲状态。如果是多线程,是线程瓶颈,那么其余线程则可以使用cpu,而非等待IO结束。 题外话,一个空循环就能让cpu满载,参看 为什么一个空的死循环会让CPU占用达到100%。 后来,出现了多路复用之类的技术,原先需要等待IO返回的线程也不需要等了,可以和其它线程一样忙别的事,IO返回时得到通知再处理接下去的事情。Java的NIO和.Net的async/await就是这么干的。 一般来说,为了避免线程频繁创建销毁带来的性能问题,程序里都会使用到线程池。 然而还是在单核的场景下,事情似乎变得有点诡异。既然线程们现在都能心无旁骛地使用CPU计算,而前面也说了,一个cpu内核同时只能运行一个线程,管理多线程又是抢占式,又是栈切换,维护生命周期啥的,影响性能不说,完全没得必要嘛,为什么不只用一个线程完成所有的计算呢。什么,你说可能需要[伪]并行计算?那就让线程自己来安排咯,毕竟具体逻辑方面,线程本身(或者说开发人员)比CPU要清楚的多,知道什么时候该干什么,什么时候切换逻辑,什么时候不切换,都由线程自己说了算。于是,协程粉墨登场。 协程主要是针对单线程的一个概念(如Js、NodeJs、Python由于GIL导致的伪多线程),可以将其看作线程运行时片段。和线程类似,虽然貌似多个协程可以并行执行,一个时间仍然只能运行一个。所以,如果业务逻辑是顺序相关(串行)或者各任务对反馈及时性要求不高,那么没必要用协程,就跟没必要多线程一样。协程对比线程,除了有更好的性能外,还让开发人员对执行片段有了更好的掌控。比如Go语言,通过阻塞条件(time.sleep()、select{}等),我们可以手动将控制权转移给其它的 Go 协程 , 也可以说是告诉调度器让它去调度其它可用空闲的 Go 协程(Go如何判断这是阻塞代码尚未研究过);或者通过channel调度指定协程。 Go默认情况下只用单线程。这就是说,你即使开了几百个goroutine,系统中同一时间在跑的只有一个线程,也就是一个协程。依据上面的内容,大家可以思考下Go为何默认如此。我们可以通过 runtime.GOMAXPROCS() 设置的是Go语言能跑几个线程,讲道理,CPU几核跑几个线程比较合理,使用 runtime.NumCPU() 查看内核数。 在编程层面来说,协程的概念偏向于以同步编程的模式实现异步处理的编程模式,避免了多层回调代码嵌套的问题。 其实在很多年以前,协程已经被提出了,现在只是它焕发生机的阶段。 4 上文说了,协程之间应该是非顺序相关的,即它们的上下文没有强依赖关系,是相对独立的。这里的上下文指的就是当前的运行栈空间,它包括了参数、局部变量、各寄存器的值等内容。在协程切换的时候,我们要想办法将对应的上下文投射到当前线程的运行栈中,即让线程执行特定的上下文。很容易想到malloc一块临时内存存放挂起的协程上下文信息,resume的时候再覆盖回去,运行栈在内存中只有一处,这就是stackless模式。相对的还有stackful模式,在这种模式下,每个协程都有自己的栈空间,运行栈指的就是当前协程的栈空间。现有语言的实现中,Python, Kotlin等定义的就是stackless协程, Go语言中实现的是stackful协程。 对于其它没有在语言层面直接支持协程的语言来说,由于协程涉及到底层的[堆]栈切换控制,因此很难单纯依靠现有语法构建算法的方式实现。有人做过此类尝试(可参看Coroutines in C),但也没有实用性。 能直接操作执行堆栈并暴露api的,现在市面上的语言以C/C++最为流行,基于它们也有很多开源的协程库。下面介绍几种实现方式。 协程分为非对称协程和对称协程。在非对称协程中,调用者和被调用者的关系是固定的,调用者将控制流转到被调用者,被调用者运行完毕后只能返回到调用者,而不能返回到其他协程。对称协程则不然。对称协程可以很容易由非对称协程来表达。且按一般的调用逻辑,A调B,B应返回到A,再由A发起到C的调用,而非B直接返回到C。因此,目前大多数协程库都只实现非对称协程。
关于汇编语法的平台差异,类Unix下采用的是AT&T的汇编语法格式,Dos/Windows下面采用的是Intel汇编语法格式。 参考资料: |
|
来自: python_lover > 《待分类》