GJM 程序 執行緒和協程的理解

2022-02-28 13:39:13 字數 3000 閱讀 8907

我的部落格:  -  傳送門:【點選前往】

程序、執行緒和協程之間的關係和區別也困擾我一陣子了,最近有一些心得,寫一下。

程序擁有自己獨立的堆和棧,既不共享堆,亦不共享棧,程序由作業系統排程。

執行緒擁有自己獨立的棧和共享的堆,共享堆,不共享棧,執行緒亦由作業系統排程(標準執行緒是的)。

協程和執行緒一樣共享堆,不共享棧,協程由程式設計師在協程的**裡顯示排程。

程序和其他兩個的區別還是很明顯的。

協程和執行緒的區別是:協程避免了無意義的排程,由此可以提高效能,但也因此,程式設計師必須自己承擔排程的責任,同時,協程也失去了標準執行緒使用多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 秒。

乙個實際一點的例子:thread.py

#!/usr/bin/python

# python thread.py

# python -m gevent.monkey thread.py

import threading

class thread(threading.thread):

def __init__(self, name):

threading.thread.__init__(self)

self.name = name

def run(self):

for i in xrange(10):

print self.name

threada = thread("a")

threadb = thread("b")

threada.start()

threadb.start()

執行:

python thread.py
如果你的輸出是均勻的:

aba

b...

那麼總共發生了 20 次切換:主線程 -> a -> b -> a -> b …

再看乙個協程的例子:gr.py

#!/usr/bin/python

# python gr.py

import greenlet

def run(name, nextgreenlets):

for i in xrange(10):

print name

if nextgreenlets:

nextgreenlets.pop(0).switch(chr(ord(name) + 1), nextgreenlets)

greenleta = greenlet.greenlet(run)

greenletb = greenlet.greenlet(run)

greenleta.switch('a', [greenletb])

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 ,所有和使用者互動的程式都會有這個問題。

程序,執行緒和協程

程序 process 和執行緒 thread 是程式設計師必需掌握的核心知識。而多程序和多執行緒程式設計對於 的並行併發執行,提公升 效率和縮短執行時間至關重要。簡單總結 程序是資源分配的單位 執行緒是作業系統排程的單位 程序切換需要的資源很最大,效率很低 執行緒切換需要的資源一般,效率一般 當然了...

程序 執行緒和協程的區別

這是我的第一篇部落格,文章如有不足之處,請各位大神多多指教!執行緒和程序的操作是由程式觸發系統介面,最後的執行者是系統 協程的操作則是程式設計師。切換開銷 即排程和切換的時間 程序 執行緒 協程 程序和執行緒的區別 概要 計算密集型 cpu bound 適合多程序,而多執行緒是雞肋 和i o密集型 ...

Python的執行緒 程序和協程

程序 乙個程序就是乙個正在執行的程式,它是計cpu分配資源的最小單位。每個程序都有自己獨立的記憶體空間。能同時執行的程序數最多不超過核心數,也就是每個核心 同一時刻只能執行乙個程序。那麼多程序就是能 同時 執行多個程序 比如同時聽 和寫 這裡的 同時 可以指cpu通過極快地在程序間來回切換來實現,所...