本文轉自:http://blog.csdn.net/hairetz/article/details/16119911
- 進程擁有自己獨立的堆和棧,既不共享堆,亦不共享棧,進程由操作系統調度。
- 線程擁有自己獨立的棧和共享的堆,共享堆,不共享棧,線程亦由操作系統調度(標准線程是的)。
- 協程和線程一樣共享堆,不共享棧,協程由程序員在協程的代碼里顯示調度。
進程和其他兩個的區別還是很明顯的。
打個比方吧,假設有一個操作系統,是單核的,系統上沒有其他的程序需要運行,有兩個線程 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 秒。
#!/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
A B A B ...
那么總共發生了 20 次切換:主線程 -> A -> B -> A -> B …
#!/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
再來說說 python 的線程,python 的線程不是標准線程,在 python 中,一個進程內的多個線程只能使用一個 CPU 。
如果使用 gevent 包裝后的線程,程序員就不必承擔調度的責任,而 python 的線程本身就沒有使用多 CPU 的能力,那么,用 gevent 包裝后的線程,取代 python 的內置線程,不是只有避免無意義的調度,提高性能的好處,而沒有什么壞處了嗎?
答案是否定的。舉一個例子,有一個 GUI 程序,上面有兩個按鈕,一個 運算 一個 取消 ,點擊運算,會有一個運算線程啟動,不停的運算,點擊取消,會取消這個線程,如果使用 python 的內置線程或者標准線程,都是沒有問題的,即便運算線程不停的運算,調度器仍然會給 GUI 線程分配時間片,用戶可以點擊取消,然而,如果使用 gevent 包裝后的線程就完蛋了,一旦運算開始,GUI 就會失去相應,因為那個運算線程(協程)霸着 CPU 不讓位。不單是 GUI ,所有和用戶交互的程序都會有這個問題。