python多線程並發
㈠ 如何優雅的編寫python並發程序
在Python中,由於歷史原因(GIL),使得Python中多線程的效果非常不理想.GIL使得任何時刻Python只能利用一個CPU核,並
且它的調度演算法簡單粗暴:多線程中,讓每個線程運行一段時間t,然後強行掛起該線程,繼而去運行其他線程,如此周而復始,直到所有線程結束.
這使得無法有效利用計算機系統中的"局部性",頻繁的線程切換也對緩存不是很友好,造成資源的浪費.
據說Python官方曾經實現了一個去除GIL的Python解釋器,但是其效果還不如有GIL的解釋器,遂放棄.後來Python官方推出了"利
用多進程替代多線程"的方案,在Python3中也有concurrent.futures這樣的包,讓我們的程序編寫可以做到"簡單和性能兼得".
多進程/多線程+Queue
一般來說,在Python中編寫並發程序的經驗是:計算密集型任務使用多進程,IO密集型任務使用多進程或者多線程.另外,因為涉及到資源共享,所
以需要同步鎖等一系列麻煩的步驟,代碼編寫不直觀.另外一種好的思路是利用多進程/多線程+Queue的方法,可以避免加鎖這樣麻煩低效的方式.
現在在Python2中利用Queue+多進程的方法來處理一個IO密集型任務.
假設現在需要下載多個網頁內容並進行解析,單進程的方式效率很低,所以使用多進程/多線程勢在必行.
㈡ 如何理解python的多線程編程
線程是程序員必須掌握的知識,多線程對於代碼的並發執行、提升代碼效率和運行都至關重要。今天就分享一個黑馬程序員Python多線程編程的教程,從0開始學習python多任務編程,想了解python高並發實現,從基礎到實踐,通過知識點 + 案例教學法幫助你想你想迅速掌握python多任務。
課程內容:
1.掌握多任務實現的並行和並發
2.掌握多進程實現多任務
3.掌握多線程實現多任務
4.掌握合理搭配多進程和線程
適用人群:
1、對python多任務編程感興趣的在校生及應屆畢業生。
2、對目前職業有進一步提升要求,希望從事python人工智慧行業高薪工作的在職人員。
3、對python人工智慧行業感興趣的相關人員。
基礎課程主講內容包括:
1.python多任務編程
基礎班課程大綱:
00-課程介紹
01-多任務介紹
02-進程介紹
03-使用多進程來完成多任務
04-多進程執行帶有參數的任務
05-獲取進程的編號
06-進程注意點
07-案例-多進程實現傳智視頻文件夾多任務拷貝器
08-線程介紹
09-使用多線程執行多任務
10-線程執行帶有參數的任務
11-主線程和子線程的結束順序
12-線程之間的執行順序是無序
13-線程和進程的對比
14-案例-多線程實現傳智視頻文件夾多任務拷貝器
15-課程總結
㈢ python 多線程支持並發嗎
python3.2以前的都不支持。另外,也要看你的解釋器是什麼版本,一般的是cpython
㈣ Python多線程是什麼意思
多線程能讓你像運行一個獨立的程序一樣運行一段長代碼。這有點像調用子進程(subprocess),不過區別是你調用shu的是一個函數或者一個類,而不是獨立的程序。
程基本上是一個獨立執行流程。單個進程可以由多個線程組成。程序中的每個線程都執行特定的任務。例如,當你在電腦上玩游戲時,比如說國際足聯,整個游戲是一個單一的過程。,但它由幾個線程組成,負責播放音樂、接收用戶的輸入、同步運行對手等。所有這些都是單獨的線程,負責在同一個程序中執行這些不同的任務。
每個進程都有一個始終在運行的線程。這是主線。這個主線程實際上創建子線程對象。子線程也由主線程啟動。
㈤ Python並發編程之創建多線程的幾種方法
Django: Py Web應用開發框架
Diesel:基於Greenlet的事件I/O框架
Flask:一個用Py編寫的輕量級Web應用框架
Cubes:輕量級Py OLAP框架
Kartograph.py:創造矢量地圖的輕量級Py框架
Pulsar:Py的事件驅動並發框架
Web2py:全棧式Web框架
Falcon:構建雲API和網路應用後端的高性能Py框架
Dpark:Py版的Spark
Buildbot:基於Py的持續集成測試框架
Zerorpc:基於ZeroMQ的高性能分布式RPC框架
Bottle: 微型Py Web框架
Tornado:非同步非阻塞IO的Py Web框架
webpy: 輕量級的Py Web框架
Scrapy:Py的爬蟲框架
㈥ 如何使用Python實現並發編程
多線程幾乎是每一個程序猿在使用每一種語言時都會首先想到用於解決並發的工具(JS程序員請迴避),使用多線程可以有效的利用CPU資源(Python例外)。然而多線程所帶來的程序的復雜度也不可避免,尤其是對競爭資源的同步問題。
然而在python中由於使用了全局解釋鎖(GIL)的原因,代碼並不能同時在多核上並發的運行,也就是說,Python的多線程不能並發,很多人會發現使用多線程來改進自己的Python代碼後,程序的運行效率卻下降了,這是多麼蛋疼的一件事呀!如果想了解更多細節,推薦閱讀這篇文章。實際上使用多線程的編程模型是很困難的,程序員很容易犯錯,這並不是程序員的錯誤,因為並行思維是反人類的,我們大多數人的思維是串列(精神分裂不討論),而且馮諾依曼設計的計算機架構也是以順序執行為基礎的。所以如果你總是不能把你的多線程程序搞定,恭喜你,你是個思維正常的程序猿:)
Python提供兩組線程的介面,一組是thread模塊,提供基礎的,低等級(Low Level)介面,使用Function作為線程的運行體。還有一組是threading模塊,提供更容易使用的基於對象的介面(類似於Java),可以繼承Thread對象來實現線程,還提供了其它一些線程相關的對象,例如Timer,Lock
使用thread模塊的例子
import thread
def worker():
"""thread worker function"""
print 'Worker'
thread.start_new_thread(worker)
使用threading模塊的例子
import threading
def worker():
"""thread worker function"""
print 'Worker'
t = threading.Thread(target=worker)
t.start()
或者Java Style
import threading
class worker(threading.Thread):
def __init__(self):
pass
def run():
"""thread worker function"""
print 'Worker'
t = worker()
t.start()
㈦ 如何在Python中編寫並發程序
GIL
在Python中,由於歷史原因(GIL),使得Python中多線程的效果非常不理想.GIL使得任何時刻Python只能利用一個CPU核,並
且它的調度演算法簡單粗暴:多線程中,讓每個線程運行一段時間t,然後強行掛起該線程,繼而去運行其他線程,如此周而復始,直到所有線程結束.
這使得無法有效利用計算機系統中的"局部性",頻繁的線程切換也對緩存不是很友好,造成資源的浪費.
據說Python官方曾經實現了一個去除GIL的Python解釋器,但是其效果還不如有GIL的解釋器,遂放棄.後來Python官方推出了"利
用多進程替代多線程"的方案,在Python3中也有concurrent.futures這樣的包,讓我們的程序編寫可以做到"簡單和性能兼得".
多進程/多線程+Queue
一般來說,在Python中編寫並發程序的經驗是:計算密集型任務使用多進程,IO密集型任務使用多進程或者多線程.另外,因為涉及到資源共享,所
以需要同步鎖等一系列麻煩的步驟,代碼編寫不直觀.另外一種好的思路是利用多進程/多線程+Queue的方法,可以避免加鎖這樣麻煩低效的方式.
現在在Python2中利用Queue+多進程的方法來處理一個IO密集型任務.
假設現在需要下載多個網頁內容並進行解析,單進程的方式效率很低,所以使用多進程/多線程勢在必行.
我們可以先初始化一個tasks隊列,裡面將要存儲的是一系列dest_url,同時開啟4個進程向tasks中取任務然後執行,處理結果存儲在一個results隊列中,最後對results中的結果進行解析.最後關閉兩個隊列.
下面是一些主要的邏輯代碼.
# -*- coding:utf-8 -*-
#IO密集型任務
#多個進程同時下載多個網頁
#利用Queue+多進程
#由於是IO密集型,所以同樣可以利用threading模塊
import multiprocessing
def main():
tasks = multiprocessing.JoinableQueue()
results = multiprocessing.Queue()
cpu_count = multiprocessing.cpu_count() #進程數目==CPU核數目
create_process(tasks, results, cpu_count) #主進程馬上創建一系列進程,但是由於阻塞隊列tasks開始為空,副進程全部被阻塞
add_tasks(tasks) #開始往tasks中添加任務
parse(tasks, results) #最後主進程等待其他線程處理完成結果
def create_process(tasks, results, cpu_count):
for _ in range(cpu_count):
p = multiprocessing.Process(target=_worker, args=(tasks, results)) #根據_worker創建對應的進程
p.daemon = True #讓所有進程可以隨主進程結束而結束
p.start() #啟動
def _worker(tasks, results):
while True: #因為前面所有線程都設置了daemon=True,故不會無限循環
try:
task = tasks.get() #如果tasks中沒有任務,則阻塞
result = _download(task)
results.put(result) #some exceptions do not handled
finally:
tasks.task_done()
def add_tasks(tasks):
for url in get_urls(): #get_urls() return a urls_list
tasks.put(url)
def parse(tasks, results):
try:
tasks.join()
except KeyboardInterrupt as err:
print "Tasks has been stopped!"
print err
while not results.empty():
_parse(results)
if __name__ == '__main__':
main()
利用Python3中的concurrent.futures包
在Python3中可以利用concurrent.futures包,編寫更加簡單易用的多線程/多進程代碼.其使用感覺和Java的concurrent框架很相似(借鑒?)
比如下面的簡單代碼示例
def handler():
futures = set()
with concurrent.futures.ProcessPoolExecutor(max_workers=cpu_count) as executor:
for task in get_task(tasks):
future = executor.submit(task)
futures.add(future)
def wait_for(futures):
try:
for future in concurrent.futures.as_completed(futures):
err = futures.exception()
if not err:
result = future.result()
else:
raise err
except KeyboardInterrupt as e:
for future in futures:
future.cancel()
print "Task has been canceled!"
print e
return result
總結
要是一些大型Python項目也這般編寫,那麼效率也太低了.在Python中有許多已有的框架使用,使用它們起來更加高效.
㈧ python多線程作用
總結起來,使用多線程編程具有如下幾個優點:
進程之間不能共享內存,但線程之間共享內存非常容易。
操作系統在創建進程時,需要為該進程重新分配系統資源,但創建線程的代價則小得多。因此,使用多線程來實現多任務並發執行比使用多進程的效率高。
Python 語言內置了多線程功能支持,而不是單純地作為底層操作系統的調度方式,從而簡化了 Python 的多線程編程。
在實際應用中,多線程是非常有用的。比如一個瀏覽器必須能同時下載多張圖片;一個 Web 伺服器必須能同時響應多個用戶請求;圖形用戶界面(GUI)應用也需要啟動單獨的線程,從主機環境中收集用戶界面事件……總之,多線程在實際編程中的應用是非常廣泛的。
㈨ Python 多線程並發控制問題
python線程有兩種,類或者函數
後者很簡單,就跟pthread一樣用啊。
不要列印就好了,或者你自己維護一個print加個自旋鎖
第三個從但是開始就沒看懂
既然要加print那一定要加鎖了,否則是沒辦法
既然你想搞一個控制線程,那就由它來列印咯
㈩ python stackless 怎麼多線程並發
1 介紹
1.1 為什麼要使用Stackless
摘自stackless網站。
Note
Stackless Python 是Python編程語言的一個增強版本,它使程序員從基於線程的編程方式中獲得好處,並避免傳統線程所帶來的性能與復雜度問題。Stackless為 Python帶來的微線程擴展,是一種低開銷、輕量級的便利工具,如果使用得當,可以獲益如下:
改進程序結構
增進代碼可讀性
提高編程人員生產力
- def familyTacoNight():
- husband.eat(dinner)
- wife.eat(dinner)
- son.eat(dinner)
- daughter.eat(dinner)
- Python 2.4.3 Stackless 3.1b3 060504 (#69, May 3 2006, 19:20:41) [MSC v.1310 32
- bit (Intel)] on win32
- Type "help", "right", "credits" or "license" for more information.
- >>> import stackless
- >>>
- >>> def print_x(x):
- ... print x
- ...
- >>> stackless.tasklet(print_x)('one')
- <stackless.tasklet object at 0x00A45870>
- >>> stackless.tasklet(print_x)('two')
- <stackless.tasklet object at 0x00A45A30>
- >>> stackless.tasklet(print_x)('three')
- <stackless.tasklet object at 0x00A45AB0>
- >>>
- >>> stackless.run()
- one
- two
- three
- >>>
- Python 2.4.3 Stackless 3.1b3 060504 (#69, May 3 2006, 19:20:41) [MSC v.1310 32
- bit (Intel)] on win32
- Type "help", "right", "credits" or "license" for more information.
- >>> import stackless
- >>>
- >>> def print_three_times(x):
- ... print "1:", x
- ... stackless.schele()
- ... print "2:", x
- ... stackless.schele()
- ... print "3:", x
- ... stackless.schele()
- ...
- >>>
- >>> stackless.tasklet(print_three_times)('first')
- <stackless.tasklet object at 0x00A45870>
- >>> stackless.tasklet(print_three_times)('second')
- <stackless.tasklet object at 0x00A45A30>
- >>> stackless.tasklet(print_three_times)('third')
- <stackless.tasklet object at 0x00A45AB0>
- >>>
- >>> stackless.run()
- 1: first
- 1: second
- 1: third
- 2: first
- 2: second
- 2: third
- 3: first
- 3: second
- 3: third
- >>>
能夠在微進程之間交換信息。
能夠控制運行的流程。
- C:>c:python24python
- Python 2.4.3 Stackless 3.1b3 060504 (#69, May 3 2006, 19:20:41) [MSC v.1310 32
- bit (Intel)] on win32
- Type "help", "right", "credits" or "license" for more information.
- >>> import stackless
- >>>
- >>> channel = stackless.channel()
- >>>
- >>> def receiving_tasklet():
- ... print "Recieving tasklet started"
- ... print channel.receive()
- ... print "Receiving tasklet finished"
- ...
- >>> def sending_tasklet():
- ... print "Sending tasklet started"
- ... channel.send("send from sending_tasklet")
- ... print "sending tasklet finished"
- ...
- >>> def another_tasklet():
- ... print "Just another tasklet in the scheler"
- ...
- >>> stackless.tasklet(receiving_tasklet)()
- <stackless.tasklet object at 0x00A45B30>
- >>> stackless.tasklet(sending_tasklet)()
- <stackless.tasklet object at 0x00A45B70>
- >>> stackless.tasklet(another_tasklet)()
- <stackless.tasklet object at 0x00A45BF0>
- >>>
- >>> stackless.run()
- Recieving tasklet started
- Sending tasklet started
- send from sending_tasklet
- Receiving tasklet finished
- Just another tasklet in the scheler
- sending tasklet finished
- >>>
- >>> stackless.tasklet(sending_tasklet)()
- <stackless.tasklet object at 0x00A45B70>
- >>> stackless.tasklet(another_tasklet)()
- <stackless.tasklet object at 0x00A45BF0>
- >>>
- >>> stackless.run()
- Sending tasklet started
- Just another tasklet in the scheler
- >>>
- >>> stackless.tasklet(another_tasklet)()
- <stackless.tasklet object at 0x00A45B30>
- >>> stackless.run()
- Just another tasklet in the scheler
- >>>
- >>> #Finally adding the receiving tasklet
- ...
- >>> stackless.tasklet(receiving_tasklet)()
- <stackless.tasklet object at 0x00A45BF0>
- >>>
- >>> stackless.run()
- Recieving tasklet started
- send from sending_tasklet
- Receiving tasklet finished
- sending tasklet finished
- def ping():
- print "PING"
- pong()
- def pong():
- print "PONG"
- ping()
- ping()
- #
- # pingpong_stackless.py
- #
- import stackless
- ping_channel = stackless.channel()
- pong_channel = stackless.channel()
- def ping():
- while ping_channel.receive(): #在此阻塞
- print "PING"
- pong_channel.send("from ping")
- def pong():
- while pong_channel.receive():
- print "PONG"
- ping_channel.send("from pong")
- stackless.tasklet(ping)()
- stackless.tasklet(pong)()
- # 我們需要發送一個消息來初始化這個游戲的狀態
- # 否則,兩個微進程都會阻塞
- stackless.tasklet(ping_channel.send)('startup')
- stackless.run()
- import thread
- import random
- import sys
- import Queue
- class hackysacker:
- counter = 0
- def __init__(self,name,circle):
- self.name = name
- self.circle = circle
- circle.append(self)
- self.messageQueue = Queue.Queue()
- thread.start_new_thread(self.messageLoop,())
- def incrementCounter(self):
- hackysacker.counter += 1
- if hackysacker.counter >= turns:
- while self.circle:
- hs = self.circle.pop()
- if hs is not self:
- hs.messageQueue.put('exit')
- sys.exit()
- def messageLoop(self):
- while 1:
- message = self.messageQueue.get()
- if message == "exit":
- debugPrint("%s is going home" % self.name)
- sys.exit()
- debugPrint("%s got hackeysack from %s" % (self.name, message.name))
- kickTo = self.circle[random.randint(0,len(self.circle)-1)]
- debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))
- self.incrementCounter()
- kickTo.messageQueue.put(self)
- def debugPrint(x):
- if debug:
- print x
- debug=1
- hackysackers=5
- turns = 5
以上是Stackless Python很簡明的釋義,但其對我們意義何在?——就在於Stackless提供的並發建模工具,比目前其它大多數傳統編程語言所提供的,都更加易用: 不僅是Python自身,也包括Java、C++,以及其它。盡管還有其他一些語言提供並發特性,可它們要麼是主要用於學術研究的(如 Mozart/Oz),要麼是罕為使用、或用於特殊目的的專業語言(如Erlang)。而使用stackless,你將會在Python本身的所有優勢之 上,在一個(但願)你已經很熟悉的環境中,再獲得並發的特性。
這自然引出了個問題:為什麼要並發?
1.1.1 現實世界就是並發的
現實世界就是「並發」的,它是由一群事物(或「演員」)所組成,而這些事物以一種對彼此所知有限的、鬆散耦合的方式相互作用。傳說中面向對象編程有 一個好處,就是對象能夠對現實的世界進行模擬。這在一定程度上是正確的,面向對象編程很好地模擬了對象個體,但對於這些對象個體之間的交互,卻無法以一種 理想的方式來表現。例如,如下代碼實例,有什麼問題?
第一印象,沒問題。但是,上例中存在一個微妙的安排:所有事件是次序發生的,即:直到丈夫吃完飯,妻子才開始吃;兒子則一直等到母親吃完才吃;而女 兒則是最後一個。在現實世界中,哪怕是丈夫還堵車在路上,妻子、兒子和女兒仍然可以該吃就吃,而要在上例中的話,他們只能餓死了——甚至更糟:永遠沒有人 會知道這件事,因為他們永遠不會有機會拋出一個異常來通知這個世界!
1.1.2 並發可能是(僅僅可能是)下一個重要的編程範式
我個人相信,並發將是軟體世界裡的下一個重要範式。隨著程序變得更加復雜和耗費資源,我們已經不能指望摩爾定律來每年給我們提供更快的CPU了,當 前,日常使用的個人計算機的性能提升來自於多核與多CPU機。一旦單個CPU的性能達到極限,軟體開發者們將不得不轉向分布式模型,靠多台計算機的互相協 作來建立強大的應用(想想GooglePlex)。為了取得多核機和分布式編程的優勢,並發將很快成為做事情的方式的事實標准。
1.2 安裝stackless
安裝Stackless的細節可以在其網站上找到。現在Linux用戶可以通過SubVersion取得源代碼並編譯;而對於Windows用戶, 則有一個.zip文件供使用,需要將其解壓到現有的Python安裝目錄中。接下來,本教程假設Stackless Python已經安裝好了,可以工作,並且假設你對Python語言本身有基本的了解。
2 stackless起步
本章簡要介紹了stackless的基本概念,後面章節將基於這些基礎,來展示更加實用的功能。
2.1 微進程(tasklet)
微進程是stackless的基本構成單元,你可以通過提供任一個Python可調用對象(通常為函數或類的方法)來建立它,這將建立一個微進程並將其添加到調度器。這是一個快速演示:
注意,微進程將排起隊來,並不運行,直到調用stackless.run()。
2.2 調度器(scheler)
調度器控制各個微進程運行的順序。如果剛剛建立了一組微進程,它們將按照建立的順序來執行。在現實中,一般會建立一組可以再次被調度的微進程,好讓每個都有輪次機會。一個快速演示:
注意:當調用stackless.schele()的時候,當前活動微進程將暫停執行,並將自身重新插入到調度器隊列的末尾,好讓下一個微進程被執行。一旦在它前面的所有其他微進程都運行過了,它將從上次 停止的地方繼續開始運行。這個過程會持續,直到所有的活動微進程都完成了運行過程。這就是使用stackless達到合作式多任務的方式。
2.3 通道(channel)
通道使得微進程之間的信息傳遞成為可能。它做到了兩件事:
又一個快速演示:
接收的微進程調用channel.receive()的時候,便阻塞住,這意味著該微進程暫停執行,直到有信息從這個通道送過來。除了往這個通道發送信息以外,沒有其他任何方式可以讓這個微進程恢復運行。
若有其他微進程向這個通道發送了信息,則不管當前的調度到了哪裡,這個接收的微進程都立即恢復執行;而發送信息的微進程則被轉移到調度列表的末尾,就像調用了stackless.schele()一樣。
同樣注意,發送信息的時候,若當時沒有微進程正在這個通道上接收,也會使當前微進程阻塞:
發送信息的微進程,只有在成功地將數據發送到了另一個微進程之後,才會重新被插入到調度器中。
2.4 總結
以上涵蓋了stackless的大部分功能。似乎不多是吧?——我們只使用了少許對象,和大約四五個函數調用,來進行操作。但是,使用這種簡單的API作為基本建造單元,我們可以開始做一些真正有趣的事情。
3 協程(coroutine)
3.1 子常式的問題
大多數傳統編程語言具有子常式的概念。一個子常式被另一個常式(可能還是其它某個常式的子常式)所調用,或返回一個結果,或不返回結果。從定義上說,一個子常式是從屬於其調用者的。
見下例:
有經驗的編程者會看到這個程序的問題所在:它導致了堆棧溢出。如果運行這個程序,它將顯示一大堆討厭的跟蹤信息,來指出堆棧空間已經耗盡。
3.1.1 堆棧
我仔細考慮了,自己對C語言堆棧的細節究竟了解多少,最終還是決定完全不去講它。似乎,其他人對其所嘗試的描述,以及圖表,只有本身已經理解了的人才能看得懂。我將試著給出一個最簡單的說明,而對其有更多興趣的讀者可以從網上查找更多信息。
每當一個子常式被調用,都有一個「棧幀」被建立,這是用來保存變數,以及其他子常式局部信息的區域。於是,當你調用 ping() ,則有一個棧幀被建立,來保存這次調用相關的信息。簡言之,這個幀記載著 ping 被調用了。當再調用 pong() ,則又建立了一個棧幀,記載著 pong 也被調用了。這些棧幀是串聯在一起的,每個子常式調用都是其中的一環。就這樣,堆棧中顯示: ping 被調用所以 pong 接下來被調用。顯然,當 pong() 再調用 ping() ,則使堆棧再擴展。下面是個直觀的表示:
幀 堆棧
1 ping 被調用
2 ping 被調用,所以 pong 被調用
3 ping 被調用,所以 pong 被調用,所以 ping 被調用
4 ping 被調用,所以 pong 被調用,所以 ping 被調用,所以 pong 被調用
5 ping 被調用,所以 pong 被調用,所以 ping 被調用,所以 pong 被調用,所以 ping 被調用
6 ping 被調用,所以 pong 被調用,所以 ping 被調用,所以 pong 被調用,所以 ping 被調用……
現在假設,這個頁面的寬度就表示系統為堆棧所分配的全部內存空間,當其頂到頁面的邊緣的時候,將會發生溢出,系統內存耗盡,即術語「堆棧溢出」。
3.1.2 那麼,為什麼要使用堆棧?
上例是有意設計的,用來體現堆棧的問題所在。在大多數情況下,當每個子常式返回的時候,其棧幀將被清除掉,就是說堆棧將會自行實現清理過程。這一般 來說是件好事,在C語言中,堆棧就是一個不需要編程者來手動進行內存管理的區域。很幸運,Python程序員也不需要直接來擔心內存管理與堆棧。但是由於 Python解釋器本身也是用C實現的,那些實現者們可是需要擔心這個的。使用堆棧是會使事情方便,除非我們開始調用那種從不返回的函數,如上例中的,那 時候,堆棧的表現就開始和程序員別扭起來,並耗盡可用的內存。
3.2 走進協程
此時,將堆棧弄溢出是有點愚蠢的。 ping() 和 pong() 本不是真正意義的子常式,因為其中哪個也不從屬於另一個,它們是「協程」,處於同等的地位,並可以彼此間進行無縫通信。
幀 堆棧
1 ping 被調用
2 pong 被調用
3 ping 被調用
4 pong 被調用
5 ping 被調用
6 pong 被調用
在stackless中,我們使用通道來建立協程。還記得嗎,通道所帶來的兩個好處中的一個,就是能夠控制微進程之間運行的流程。使用通道,我們可以在 ping 和 pong 這兩個協程之間自由來回,要多少次就多少次,都不會堆棧溢出:
你可以運行這個程序要多久有多久,它都不會崩潰,且如果你檢查其內存使用量(使用Windows的任務管理器或Linux的top命令),將會發現 使用量是恆定的。這個程序的協程版本,不管運行一分鍾還是一天,使用的內存都是一樣的。而如果你檢查原先那個遞歸版本的內存用量,則會發現其迅速增長,直 到崩潰。
3.3 總結
是否還記得,先前我提到過,那個代碼的遞歸版本,有經驗的程序員會一眼看出毛病。但老實說,這裡面並沒有什麼「計算機科學」方面的原因在阻礙它的正 常工作,有些讓人堅信的東西,其實只是個與實現細節有關的小問題——只因為大多數傳統編程語言都使用堆棧。某種意義上說,有經驗的程序員都是被洗了腦,從 而相信這是個可以接受的問題。而stackless,則真正察覺了這個問題,並除掉了它。
4 輕量級線程
與當今的操作系統中內建的、和標准Python代碼中所支持的普通線程相比,「微線程」要更為輕量級,正如其名稱所暗示。它比傳統線程佔用更少的內存,並且微線程之間的切換,要比傳統線程之間的切換更加節省資源。
為了准確說明微線程的效率究竟比傳統線程高多少,我們用兩者來寫同一個程序。
4.1 hackysack模擬
Hackysack是一種游戲,就是一夥臟乎乎的小子圍成一個圈,來回踢一個裝滿了豆粒的沙包,目標是不讓這個沙包落地,當傳球給別人的時候,可以耍各種把戲。踢沙包只可以用腳。
在我們的簡易模擬中,我們假設一旦游戲開始,圈裡人數就是恆定的,並且每個人都是如此厲害,以至於如果允許的話,這個游戲可以永遠停不下來。
4.2 游戲的傳統線程版本