進程、線程和協程之間的關係和區別也困擾我一陣子了,最近有一些心得,寫一下。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性能
運行:spa
python thread.py
若是你的輸出是均勻的:操作系統
那麼總共發生了 20 次切換:主線程 -> A -> B -> A -> B …
再看一個協程的例子:gr.py
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 ,全部和用戶交互的程序都會有這個問題。
https://blog.csdn.net/hairetz/article/details/16119911