python3 非同步處理:thread, process 與concurrent.futures的兩三事
使用python進行非同步處理一直以來都被認為是較為複雜的地方,趁著空閑之餘整理消化一下官方文件上的資訊並記錄下來。範圍是較為基礎的Concurrent Execution章節(17.1~17.4),包含threading, multiprocessing 與 future。
python要進行非同步處理大多直覺會使用多個執行緒,但若是執行的是cpu bound的工作很快就會發現cpu怎麼沒有跑好跑滿。google一下會發現原來是cpython的GIL在作祟,導致只能使用單一的cpu core。若要避免GIL,可以改用jython(JVM會使用所有core來處理threads)或是使用python為了cpu core利用最大化而提出來的multiprocess package。使用上,multiprocess與multithread介面完全一致,但底層觀念就不一樣了。
複習一下作業系統program/process/thread觀念,program執行被load到記憶體以一個或多個process的形式存在。process是thread的容器,同一個process間的thread共用資源而process間的資源彼此獨立。因此,同一個process的thread也共用同一個GIL,要避開GIL就使用多個process就好了。multiprocess比起multithread最大的優點是不受GIL限制,cpu bound的task執行會很快。而最大的缺點就是process間彼此無法共享資源、溝通會受到限制。
不管是multithread或是multiprocess,都需要耗費資源去產生thread/process,而產生process的成本又比產生thread的成本高。如果非同步處理的task比較偏向io-bound,那產生thread/process的時間可能都比執行task的時間還要長。這時候可能就不適合使用multithread/multiprocess了。處理非同步io-bound的task,最新的推薦做法是使用async/await syntax,前身則是官方的asyncio package以及concurrent package,其核心觀念為event loop, coroutines, tasks。io的非同步處理就不介紹下去了,接下來會來介紹concurrent.futures package,順便為asyncio補點前置的基礎知識。
引子講了這麼多,要開始進入本章的主題 concurrent futures了!
先給個多執行緒的範例:
thread_list = []
for arg in args:
thread_list.append(Thread(target=foo, args=(arg, )))
for thread in thread_list:
thread.start()
for thread in thread_list:
thread.join()我只是要非同步平行執行 foo function而已,怎麼寫起來落落長…
該學會使用concurrent.futures了!這個module提供一種更高階的介面來操作非同步執行,內容大致上可以分成兩個部分:executor object, future object。
Executor
Executer主要負責執行非同步的”call”,目前有ThreadPoolExecutor跟ProcessPoolExecutor兩個類別,分別對應multithread與multiprocess。
Future
Future Object有點像是Node.js的Promise, 他是一種封裝後的執行要求,共有三種狀態(done, cancel, exception),只能透過executer.sumbit來產生。
Example
前面提到的example可以利用concurrent.future改寫成
with concurrent.futures.ProcessPoolExecutor() as executor:
futures = [executor.submit(foo, arg) for arg in args]
concurrent.futures.wait(futures, return_when=ALL_COMPLETED)大致上是利用executor的submit方法來包裝foo(arg)成為future,最後將所有future同時執行並等待所有future結束。而concurrent.futures.wait 的 return_when參數還有另外兩個: FIRST_EXCEPTION, FIRST_COMPLETED
- return_when=ALL_COMPLETED 類似Node.js的 Promise.all
- return_when=FIRST_COMPLETED 則類似於 Promise.race
以上是關於concurrent.future的簡單介紹,其中future這個觀念可以延伸到更進階的coroutines與asyncio,有機會的話在另文說明。
