當前位置:
首頁 > 新聞 > 尹立博:Python 全局解釋器鎖與並發

尹立博:Python 全局解釋器鎖與並發

雷鋒網 AI 科技評論按:作為排名靠前的最受歡迎和增長最快的編程語言之一,Python 是一種多用途、高級別、面向對象、互動式、解釋型和對用戶非常友好的編程語言,擁有卓越的可讀性和極高的自由度。而為了能利用多核多線程的的優勢,同時又要保證線程之間數據完整性和狀態同步,Python 官方的、最廣泛使用的解釋器——CPython 往往會採取最簡單的加鎖的方式——全局解釋器鎖(GIL)。

然而,GIL 的設計有時會顯得笨拙低效,並對語言的並發性帶來嚴重限制,但是此時由於內置庫和第三方庫已經對 GIL 形成了巨大的依賴,想改變 GIL 反而變得困難了。不過實際上,Python 生態系統中存在諸多工具可以解決這一問題。

尹立博:畢業於西澳大利亞大學和澳大利亞國立大學。現在堪培拉 Seeing Machines 公司擔任數據分析師,日常使用 Python 數據工具對大量時序數據進行管理、分析與可視化開發。

分享主題:Python 全局解釋器鎖與並發

分享提綱:

1、全局解釋器鎖 (GIL)

2、多進程 (multiprocessing)

3、多線程 (multithreading)

4、非同步 (async)

5、分散式計算(以 Dask 為例)

雷鋒網 AI 研習社將其分享內容整理如下:

今天要跟大家分享的是 Python 全局解釋器鎖與並發。我會先介紹一下全局解釋器鎖 (GIL))的概念和影響;接下來會藉助幾個案例分析來展示 Python 通過多進程、多線程和非同步、分散式計算來達成並發的幾種方式;最後會介紹一套分散式計算工具——Dask。


全局解釋器鎖 (GIL)

GIL 的概念用簡單的一句話來解釋,就是「任一時刻,無論線程多少,單一 CPython 解釋器只能執行一條位元組碼」。這個定義需要注意的點包括:

第一,GIL 不屬於 Python 語言定義,而是 CPython 解釋器實現的一部分;

第二,其他 Python 解釋器不一定有 GIL。例如 Jython (JVM) 和 IronPython (CLR) 沒有 GIL,而 PyPy 有 GIL;

第三,GIL 並不是 Python 的專利。其他語言也有 GIL,尤其是動態語言,如 Ruby MRI。

說到 GIL,就不得不提 Python 線程模型,它的運行方式如下:

CPython 使用 OS 原生線程,由 OS 負責調度;

每個解釋器進程有唯一的主線程和用戶定義的任意數量子線程;

GIL 是位元組碼層面上的互斥鎖。剛剛定義中提到的 PyThread_type_lock 就是 OS 互斥鎖的別名

每個解釋器進程有且僅有一把鎖;

當解釋器啟動時,主線程即獲取 GIL;

一個線程持有 GIL 並執行位元組碼時,其他線程處於阻塞狀態。

GIL 被加到 CPython 解釋器中,是有其原因的。在 1992 年,單 CPU 是合理的假設!多核則是 2005-2006 年前後才普及,此外,GIL 的優勢還包括:

簡化解釋器實現;

優化單進程性能;

簡化 C 擴展庫的整合。

Python 有兩種多任務模型:一種叫做協作式 (cooperative) 多任務;另一種叫搶佔式 (preemptive) 多任務。

協作式多任務:

在 I/O 前主動釋放 GIL,I/O 之後重新獲取。這可以在 C 源代碼中使用 Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS 宏實現

這種多任務方式能夠提升代碼性能!

搶佔式多任務:

間歇性掛起活躍進程,交由 OS 重新調度

Python 2:每執行 100 個位元組碼,當前進程就會被掛起

Python 3.2+: 每隔 5 毫秒

這種多任務方式不提高代碼性能,但使得多個任務能在同一時間段內執行

接下來可以進展到去除 GIL。這是很多 Python 用戶十分期待的事情,但是短期內是不太可能實現的,它的難點包括:

第一,技術問題

Guido 要求不降低單線程執行效率

兼容現有引用計數與垃圾回收機制

兼容現有 C 擴展

第二,在社區友好性上,不顯著提高開發難度。

儘管如此,我們也可以看到一些現有去除 GIL 的實驗性的方案:

Gilectomy:嘗試將 GIL 換成若干小鎖,然而這種方案嚴重降低了 Python 的性能。首先,它會使得多線程競爭同一把鎖。其次,它在將 GIL 換成若干小鎖後,將嚴重降低緩存的命中率。

PyPy:實驗性分支支持軟體事務內存 (STM),不過 STM 目前還是一個相對少見的機制,可解決當前很多問題,但是實現非常困難——尤其在像 Python 這種高度動態的語言當中。

Starlark:這種方案並非去掉 GIL,而是一門兼容部分 Python 語法,並發執行位元組碼的新語言。它目前用於 Google Bazel 編譯系統,我個人認為這是一個非常有意思的未來趨勢。

既然現在去除 GIL 的方案都有很多弊端,並且短期內我們也無法讓 GIL 從 Python 中被去除,我們最常見的解決方案就是避開 GIL,主要通過兩種手段實現:

第一種是多解釋器進程並發 (multiprocessing)

第二種是避免執行 Python 位元組碼,常見的方法有:Cython ctypes、部分 NumPy 函數釋放 GIL、Numba JIT「nogil=True」,以及 TensorFlow/PyTorch JIT。


多進程(multiprocessing)和多線程(multithreading)

進入案例分析前,先介紹幾個相關的概念。

首先介紹一下並行與並發的區別:

並發(concurrency):是指多個操作可以在重疊的時間段內進行,例如在第一個時間片內,線程 A 執行,線程 B 阻塞;第二個時間片內,線程 B 等待 I/O,而線程 A 執行;第三個時間片內,線程 A 執行,而線程 B 還在等待 I/O。

並行(parallelism):是指多個操作在同一時間點上進行。無論在哪個時間片里,兩個線程可能同時處於某一狀態。例如在第一個時間片內,線程 A 執行,線程 B 執行;第二個時間片內,線程 A 等待 I/O,線程 B 也在等待 I/O ;第三個時間片內,線程 A 執行,而線程 B 也 執行。

多線程意味著我們在使用並發這種線程模型,而多進程則是在使用並行這一線程模型,其各有利弊:

多線程並發的優勢為:可共享內存空間,方便交換數據;劣勢為:會同時寫入內存將導致數據損壞。

多進程並行的優勢為:內存空間獨立(恰來自其劣勢);劣勢為:進程間交互需要序列化-通信-反序列化。

接下來我們將通過一個案例來嘗試 Python 並發的幾種不同解決方案的案例:

(關於嘗試 Python 並發的幾種不同解決方案的案例講解,請回看視頻 00:19:05 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

這就講到多進程(multiprocessing)這一概念,它的適用場景包括:

CPU 佔用率高

子進程間通信簡單

相關變數和函數可被序列化,但佔用內存較小

如果想知道更多內容,大家可參見文檔:

接下來進入到多進程解決方案的案例講解:

(關於多進程解決方案的案例講解,請回看視頻 00:23:25 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

之後要講到多線程 (multithreading),多線程的使用場景包括:

CPU 佔用率低

I/O 負載高

子任務需要共享內存

如要了解更多內容,可以參見文檔:

(關於多線程解決方案的案例講解,請回看視頻 00:33:25 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

第一,CPython 的線程切換可能在任意位元組碼之間發生,而 Python 指令不具有原子性

第二,每次訪問受限資源都需獲取鎖

第三,鎖不具有強制性,即使忘記獲取鎖,代碼也可能運行

第四,競爭狀態難以複製

我們看一個相關的案例——多線程計數器:

(關於多線程計數器的案例講解,請回看視頻 00:37:00 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo


非同步 (async)

接著講一下非同步 (async)。Python 中的非同步是一種在單一線程內使用生成器實現的協程,比線程能更高效地組織非阻塞式任務。協程的切換由 Python 解釋器內完成。當然,其他語言也有非同步編程,比如 Go 語言的 goroutine,以及 Nginx 用 C 實現了非同步編程。

關於更多非同步編程的內容,大家可參見文檔:

看案例之前,先比較一下非同步與線程。與線程相比,非同步的優劣勢分別為:

優勢:

簡單的多任務模型

明確的協程切換點

系統開銷遠小於 OS 原生線程

劣勢:

有相對獨立的生態系統

與其他並發模型混用較難

API 仍未穩定

下面我們看非同步的案例:

(關於非同步的案例講解,請回看視頻 00:46:05 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo


分散式計算(以 Dask 為例)

最後講一下分散式計算,本堂課中的分散式計算以 Dask 為例。

Dask 是一種基於運算圖的動態任務調度器,可使用動態調度器擴展 NumPy 和 Pandas。左邊這個圖就是 Dask 的運算圖。

(關於 Dask 運算圖的講解,請回看視頻 00:55:45 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo)

與另一種分散式計算方法 Spark 比較,Dask 的特性非常鮮明:

它是一個純 Python 實現

無需遵循 map-reduce 範式

細粒調度帶來較低的延遲

在 Dask 中,我們更關注的是 Distributed。它是 Dask 在異構集群上的擴展。它的網路結構遵循客戶 – 調度器 – 工作節點這樣的形式,因此要求所有節點擁有相同的 Python 運行環境。

接下來我們看一個簡單的案例:

(關於該案例講解,請回看視頻 00:59:45 處,http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

最後放上今天這堂課涉及到的內容的演講,基本都能在 youtube 上進行觀看。

Dave Beazley: Understanding the Python GIL, PyCon 2010

Dave Beazley: Embracing the Global Interpreter Lock (GIL), PyCodeConf 2011

Larry Hastings: Python"s Infamous GIL, PyCon 2015

Larry Hastings: Removing Python"s GIL: The Gilectomy, PyCon 2016

A Jesse Jiryu Davis: Grok the GIL Write Fast And Thread Safe Python, PyCon 2017

Raymond Hettinger: Keynote on Concurrency, PyBay 2017

Dave Beazley: Fear and Awaiting in Async: A Savage Journey to the Heart of the Coroutine Dream

Robert Smallshire: Coroutine Concurrency in Python 3 with asyncio

Matthew Rocklin: Dask: A Pythonic Distributed Data Science Framework, PyCon 2017


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 雷鋒網 的精彩文章:

李斌公開回應「奶媽車」:油能運,怎麼就不讓運電?
獨家解讀|智能駕駛晶元技術路線的選擇:CPU、GPU、FPGA 以及 ASIC

TAG:雷鋒網 |