進程、線程和協程的理解

進程、線程和協程的理解

進程線程協程之間的關係和區別也困擾我一陣子了,最近有一些心得,寫一下。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

相關文章
相關標籤/搜索