进程、线程和协程的理解

进程、线程和协程的理解

进程线程协程之间的关系和区别也困扰我一阵子了,最近有一些心得,写一下。css

进程拥有本身独立的堆和栈,既不共享堆,亦不共享栈,进程由操做系统调度。html

线程拥有本身独立的栈和共享的堆,共享堆,不共享栈,线程亦由操做系统调度(标准线程是的)。python

协程和线程同样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。git

进程和其余两个的区别仍是很明显的。程序员

协程和线程的区别是:协程避免了无心义的调度,由此能够提升性能,但也所以,程序员必须本身承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。编程

打个比方吧,假设有一个操做系统,是单核的,系统上没有其余的程序须要运行,有两个线程 A 和 B ,A 和 B 在单独运行时都须要 10 秒来完成本身的任务,并且任务都是运算操做,A B 之间也没有竞争和共享数据的问题。如今 A B 两个线程并行,操做系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共须要 20 + 19 * 0.1 = 21.9 秒。若是使用协程的方式,能够先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间是 20 + 1 * 0.1 = 20.1 秒。若是系统是双核的,并且线程是标准线程,那么 A B 两个线程就能够真并行,总时间只须要 10 秒,而协程的方案仍然须要 20.1 秒。socket

一个实际一点的例子:thread.py性能

  1.  
    #!/usr/bin/python
  2.  
    # python thread.py
  3.  
    # python -m gevent.monkey thread.py
  4.  
     
  5.  
    import threading
  6.  
     
  7.  
    class Thread(threading.Thread):
  8.  
     
  9.  
    def __init__(self, name):
  10.  
    threading.Thread.__init__(self)
  11.  
    self.name = name
  12.  
     
  13.  
    def run(self):
  14.  
    for i in xrange(10):
  15.  
    print self.name
  16.  
     
  17.  
    threadA = Thread("A")
  18.  
    threadB = Thread("B")
  19.  
     
  20.  
    threadA.start()
  21.  
    threadB.start()
  22.  
     

运行:spa

python thread.py 

若是你的输出是均匀的:操作系统

  1.  
    A
  2.  
    B
  3.  
    A
  4.  
    B
  5.  
    ...

那么总共发生了 20 次切换:主线程 -> A -> B -> A -> B …

再看一个协程的例子:gr.py

  1.  
    #!/usr/bin/python
  2.  
    # python gr.py
  3.  
     
  4.  
    import greenlet
  5.  
     
  6.  
    def run(name, nextGreenlets):
  7.  
    for i in xrange(10):
  8.  
    print name
  9.  
    if nextGreenlets:
  10.  
    nextGreenlets.pop(0).switch(chr(ord(name) + 1), nextGreenlets)
  11.  
     
  12.  
    greenletA = greenlet.greenlet(run)
  13.  
    greenletB = greenlet.greenlet(run)
  14.  
     
  15.  
    greenletA.switch('A', [greenletB])
  16.  
     

greenlet 是 python 的协程实现。

运行:

python gr.py 

此时发生了 2 次切换:主协程 -> A -> B

可能你已经注意到了,还有一个命令:

python -m gevent.monkey thread.py 

gevent 是基于 greenlet 的一个 python 库,它能够把 python 的内置线程用 greenlet 包装,这样在咱们使用线程的时候,实际上使用的是协程,在上一个协程的例子里,协程 A 结束时,由协程 A 让位给协程 B ,而在 gevent 里,全部须要让位的协程都让位给主协程,由主协程决定运行哪个协程,gevent 也会包装一些可能须要阻塞的方法,好比 sleep ,好比读 socket ,好比等待锁,等等,在这些方法里会自动让位给主协程,而不是由程序员显示让位,这样程序员就能够按照线程的模式进行线性编程,不须要考虑切换的逻辑。

gevent 版的命令发生了 3 次切换:主协程 -> A -> 主协程 -> B

假设代码质量相同,用原生的协程实现须要切换 n 次,用协程包装后的线程实现,就须要 2n - 1 次,姑且算是两倍吧。很显然,单纯从效率上来讲,代码质量相同的前提下,用 gevent 永远也不可能比用 greenlet 快,然而,问题每每不那么单纯,比方说,单纯从效率上来讲,代码质量相同的前提下,用 C 实现的程序永远不可能比汇编快。

再来讲说 python 的线程,python 的线程不是标准线程,在 python 中,一个进程内的多个线程只能使用一个 CPU 。

从新来看一下协程和线程的区别:协程避免了无心义的调度,由此能够提升性能,但也所以,程序员必须本身承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

若是使用 gevent 包装后的线程,程序员就没必要承担调度的责任,而 python 的线程自己就没有使用多 CPU 的能力,那么,用 gevent 包装后的线程,取代 python 的内置线程,不是只有避免无心义的调度,提升性能的好处,而没有什么坏处了吗?

答案是否认的。举一个例子,有一个 GUI 程序,上面有两个按钮,一个 运算 一个 取消 ,点击运算,会有一个运算线程启动,不停的运算,点击取消,会取消这个线程,若是使用 python 的内置线程或者标准线程,都是没有问题的,即使运算线程不停的运算,调度器仍然会给 GUI 线程分配时间片,用户能够点击取消,然而,若是使用 gevent 包装后的线程就完蛋了,一旦运算开始,GUI 就会失去相应,由于那个运算线程(协程)霸着 CPU 不让位。不单是 GUI ,全部和用户交互的程序都会有这个问题。

 

本文转自http://blog.leiqin.name/2012/12/02/%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E7%9A%84%E7%90%86%E8%A7%A3.html

https://blog.csdn.net/hairetz/article/details/16119911

相关文章
相关标签/搜索