python3 非同步處理:thread, process 與concurrent.futures的兩三事

Felix
Felix
Sep 7, 2018 · 5 min read

使用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,有機會的話在另文說明。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade