流暢的python第十七章使用期物處理併發

2022-05-09 00:57:13 字數 3554 閱讀 6337

從 python 3.4 起,標準庫中有兩個名為 future 的類:concurrent.futures.future 和

asyncio.future。這兩個類的作用相同:兩個 future 類的例項都表示可能已經完成或

者尚未完成的延遲計算

我們要記住一件事:通常情況下自己不應該建立期物,而只能由併發框架

(concurrent.futures 或 asyncio)例項化。原因很簡單:期物表示終將發生的事

情,而確定某件事會發生的唯一方式是執行的時間已經排定。因此,只有排定把某件事交

給 concurrent.futures.executor 子類處理時,才會建立

concurrent.futures.future 例項。例如,executor.submit() 方法的引數是乙個可

呼叫的物件,呼叫這個方法後會為傳入的可呼叫物件排期,並返回乙個期物。

客戶端**不應該改變期物的狀態,併發框架在期物表示的延遲計算結束後會改變期物的

狀態,而我們無法控制計算何時結束。

這兩種期物都有 .done() 方法,這個方法不阻塞,返回值是布林值,指明期物鏈結的可

呼叫物件是否已經執行。客戶端**通常不會詢問期物是否執行結束,而是會等待通知。

因此,兩個 future 類都有 .add_done_callback() 方法:這個方法只有乙個引數,類

型是可呼叫的物件,期物執行結束後會呼叫指定的可呼叫物件。

此外,還有 .result() 方法。在期物執行結束後呼叫的話,這個方法在兩個 future 類

中的作用相同:返回可呼叫物件的結果,或者重新丟擲執行可呼叫的物件時丟擲的異常。

可是,如果期物沒有執行結束,result 方法在兩個 future 類中的行為相差很大。對

concurrency.futures.future 例項來說,呼叫 f.result() 方法會阻塞呼叫方所在的

執行緒,直到有結果可返回。此時,result 方法可以接收可選的 timeout 引數,如果在指

定的時間內期物沒有執行完畢,會丟擲 timeouterror 異常。讀到 18.1.1 節你會發

現,asyncio.future.result 方法不支援設定超時時間,在那個庫中獲取期物的結果最

好使用 yield from 結構。不過,對 concurrency.futures.future 例項不能這麼做。

這兩個庫中有幾個函式會返回期物,其他函式則使用期物,以使用者易於理解的方式實現自

身。使用 17-3 中的 executor.map 方法屬於後者:返回值是乙個迭代器,迭代器的

__next__ 方法呼叫各個期物的 result 方法,因此我們得到的是各個期物的結果,而非

期物本身。

阻塞型i/o和gil

cpython 直譯器本身就不是執行緒安全的,因此有全域性直譯器鎖(gil),一次只允許使用

乙個執行緒執行 python 位元組碼。因此,乙個 python 程序通常不能同時使用多個 cpu 核

心python 標準庫中的所有阻塞型 i/o 函式都會釋放 gil,允許其他執行緒運

行。time.sleep() 函式也會釋放 gil。因此,儘管有 gil,python 執行緒還是能在 i/o

密集型應用中發揮作用。

如何在 cpu 密集型作業中使用 concurrent.futures 模組輕鬆繞開 gil

concurrent.futures 模組的文件

(副標題是「launching parallel

tasks」(執行並行任務)。這個模組實現的是真正的平行計算,因為它使用

processpoolexecutor 類把工作分配給多個 python 程序處理。因此,如果需要做 cpu

密集型處理,使用這個模組能繞開 gil,利用所有可用的 cpu 核心。

threadpoolexecutor.__init__ 方法需要 max_workers 引數,指定執行緒池中線程

的數量。在 processpoolexecutor 類中,那個引數是可選的,而且大多數情況下不使用

——預設值是 os.cpu_count() 函式返回的 cpu 數量。這樣處理說得通,因為對 cpu 密

集型的處理來說,不可能要求使用超過 cpu 數量的職程。而對 i/o 密集型處理來說,可

以在乙個 threadpoolexecutor 例項中使用 10 個、100 個或 1000 個執行緒;最佳執行緒數

取決於做的是什麼事,以及可用記憶體有多少,因此要仔細測試才能找到最佳的執行緒數。

from time import

sleep, strftime

from concurrent import

futures

def display(*args):

print(strftime('

[%h:%m:%s]

'), end='')

print(*args)

defloiter(n):

msg = '

{}loiter{}: doing nothing for {}s...

'display(msg.format('\t

'*n, n, n))

sleep(n)

msg = '

{}loiter({}): done.

'display(msg.format('\t

'*n, n))

return n * 10

defmain():

display(

'script starting.')

executor = futures.threadpoolexecutor(max_workers=3)

results = executor.map(loiter, range(5))

display(

'result:

', results)

display(

'waiting for individual results:')

for i, result in

enumerate(results):

display(

'result {}: {}

'.format(i, result))

main()

executor.map 函式易於使用,不過有個特性可能有用,也可能沒用,具體情況取決於需

求:這個函式返回結果的順序與呼叫開始的順序一致。如果第乙個呼叫生成結果用時 10

秒,而其他呼叫只用 1 秒,**會阻塞 10 秒,獲取 map 方法返回的生成器產出的第乙個

結果。在此之後,獲取後續結果時不會阻塞,因為後續的呼叫已經結束。如果必須等到獲

取所有結果後再處理,這種行為沒問題;不過,通常更可取的方式是,不管提交的順序,

只要有結果就獲取。為此,要把 executor.submit 方法和 futures.as_completed 函

數結合起來使用

總結一下,python中有gil鎖,導致無法和正常使用執行緒,但是對於io密集型作業,由於python標準庫中的所有堵塞型i/o函式都會釋放gil,允許其他執行緒執行,所以不妨礙多執行緒的使用。而對於cpu密集型作業,可以使用concurrent.futures模組繞開gil。

第十七章 Size Classes

通常,您希望應用程式的介面根據螢幕的尺寸和方向而顯示不同的布局。在本章中,您將修改homepwner中的detailviewcontroller的介面,以便當它出現在具有相對較小高度的螢幕上時,文字字段集合和影象檢視併排而不是堆疊在一起 圖17.1 圖17.1 homepwner 的 detailv...

第十七章 部署

總的來說,部署遇到了許多坑,而且還有好多坑還沒有踩。去cocode找了許多教程,才踩了一些坑,但是關於資料庫的坑,先留著。所有的db.session.add 後面都加上db.session.commit 才行。記得,這是狗書的問題!部署教程 每次修改後需要一下命令重新部署下 git add git ...

第十七章 包

1 如果a資料夾所在目錄在環境變數,a資料夾中的 ma 模組可以被以下方式匯入 import a.ma form a import ma 2 如果 a 資料夾所在目錄在環境變數,a 資料夾中的 b 資料夾的 mb 模組可以被以下方式匯入 import a b mb from a.b import m...