天天看點

聊聊在Python如何實作并行

目錄

何為并行和并發Python有哪些相關的子產品該如何選擇合适的子產品CPU-bound和I/O-bound問題threading、asyncio和multiprocessing優劣抉擇結論

何為并行和并發

在文章開始之前先看看來自 StackOverflow 的一篇回答是如何解釋并行和并發的。(https://stackoverflow.com/questions/1050222/what-is-the-difference-between-concurrency-and-parallelism)

Concurrency is when two or more tasks can start, run, and complete in overlapping time periods. It doesn't necessarily mean they'll ever both be running at the same instant. For example, multitasking on a single-core machine.

Parallelism is when tasks literally run at the same time, e.g., on a multicore processor.

對于并發(Concurrency)和并行(Parallelism)的核心差别在于at the same time。對于Python而言,有很多與并發相關的名稱,例如thread、task、process,但其核心都一樣,簡而言之,就是按照一定順序執行的一系列指令(a sequence of instructions that run in order)。至于為啥有這麼多名稱,因為在具體執行上會有些細微差别,例如 Async IO 和 Threading 所代表的處理邏輯并不一樣。

對于并行而言就比較孤獨了,隻有 multiprocessing 。 由于Python的GIL(全局解釋鎖)的存在導緻沒有向Java等JVM語言上的真正意義上的多線程并行(除了Jython,是以這麼一看GIL也不一定是Python這門語言必須的),隻能使用 multiprocessing 拷貝上下文的多程序實作真正意義的并行,而Async IO和Threading實際是單核下的多任務排程。

Python有哪些相關的子產品

對于 threading 而言,作業系統知道每一個線程的運作情況以及擁有可以在任何時間打斷其運作,然後運作其他線程的能力,這就是所謂的pre-emptive multitasking(搶占式多任務)。顧名思義,作業系統可以在任何時間搶占并排程線程。對于搶占式任務,核心問題在于“任何時間”,但這會導緻x = x + 1這類的語句産生問題。

而 asyncio 使用事件循環這個Python對象,利用協同式方式處理多任務(cooperative multitasking),任務之間的切換取決于任務是否完成,是否已經準備好被切換。具體可以參考

https://stackoverflow.com/questions/49005651/how-does-asyncio-actually-work/51116910#51116910

至于 multiprocessing ,與 asyncio 和 threading 設計思路完全不一樣,multiprocessing 中每一個程序都擁有自己的Python解釋器以及上下文資訊,是以每一個程序都可以運作在不同的CPU核心上。

三者的簡單差別如下:

并發類型 任務選擇權 核心數
搶占式多任務(threading) 由作業系統決定任務的執行權 1
協同式多任務(asyncio) 取決于任務的執行情況 1
多核心任務(multiprocessing) 所有程序在同一時間執行任務 很多

該如何選擇合适的子產品

CPU-bound和I/O-bound問題

在決定如何選擇之前,要解決兩個主要問題CPU-bound和I/O-bound,因為所有的問題都可以歸結為這兩類問題。

對于這兩個問題,在 StackOverflow 的有一段解釋相當易懂,如下:(https://stackoverflow.com/questions/868568/what-do-the-terms-cpu-bound-and-i-o-bound-mean)

A program is CPU bound if it would go faster if the CPU were faster, i.e. it spends the majority of its time simply using the CPU (doing calculations). A program that computes new digits of π will typically be CPU-bound, it's just crunching numbers.

A program is I/O bound if it would go faster if the I/O subsystem was faster. Which exact I/O system is meant can vary; I typically associate it with disk, but of course networking or communication in general is common too. A program that looks through a huge file for some data might become I/O bound, since the bottleneck is then the reading of the data from disk (actually, this example is perhaps kind of old-fashioned these days with hundreds of MB/s coming in from SSDs).

是以,對于I/O-bound的問題,程式運作的速度主要取決于外部的輸入輸出,并且SQLAlchemy的作者有一篇經典文章(https://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases)對I/O-bound下的關于外部資料庫連接配接對程式運作的影響進行了詳細的讨論。而對于CPU-bound的問題,程式的運作速度取決于CPU運作有多塊,例如數學運算等等。

兩者的簡單對比如下:

I/O-Bound Process CPU-Bound Process
程式花費了大量在處理如網絡連接配接、磁盤讀寫等問題。 程式花了大量時間在執行CPU操作
需要在等待時間盡可能做多的事 需要盡可能的做多的CPU操作
threading 、asyncio和multiprocessing優劣

threading 子產品的核心是ThreadPoolExecutor ,拆開來看就是Thread + Pool + Executor 。Thread 是Python中專門處理線程的包,Pool是線程池,用于創造線程運作的環境,而Executor則是具體的執行者,控制線程池中的每一個線程如何運作和什麼時候運作。對于 threading ,程式需要保證資料的線程安全,例如使用Queue子產品,不僅如此,還要處理競争條件(不同線程對同一資源的争搶)。

asyncio 是Python3才引入的子產品, 在Python3.5以後又進一步引入了async和await關鍵字,3.7簡化了運作模式整合進running函數中。其核心是事件循環(event loop),事件循環控制這每一個任務如何運作以及何時運作,也就是說事件循環需要維護一個關于事件狀态的清單。對于 asyncio 而言,進一步優化了threading對線程池的排程,但局限于協同式任務的弊病,當一個任務因為某些代碼問題導緻CPU運作時間過長就會導緻其他任務無法運作。

multiprocessing 則突破了單CPU運作的局限,使Python代碼可以運作在多CPU環境,受限于GIL(https://realpython.com/python-gil/)的存在,multiprocessing 需要複制Python解釋器環境,相對于線程,付出了更為高昂的代價。

抉擇

綜上所述,對于I/O-bound的問題,如果是Python3.6以上,應該優先選擇asyncio,但是考慮到低版本Python的相容,threading 也應該被考慮進來,而CPU-bound則隻能使用 multiprocessing 操控多核進行計算。

結論

對于并發和并行問題,一直是個大問題,正如 Donald Knuth 所言:“Premature optimization is the root of all evil (or at least most of it) in programming.”(過早的優化是萬惡之源),使用threading、asyncio和multiprocessing需要對代碼做出大量的改進,隻有當運作時間帶來的影響遠遠大于修改代碼的時間時,才需要考慮引入并行,并小心翼翼的處理并行中所可能會帶來的諸如競争等問題。

參考文獻:

https://realpython.com/python-concurrency/

https://realpython.com/async-io-python/

https://en.wikipedia.org/wiki/Preemption_%28computing%29#Preemptive_multitasking

https://en.wikipedia.org/wiki/Cooperative_multitasking