經過反復討論,Python/ target=_blank class=infotextkey>Python 指導委員會打算批準一項提案——PEP 703,這項提案具體是指:"在 CPython 中使 GIL(全局解釋器鎖)成為可選項"。
該提案是多年來為移除 Python 的 GIL 而進行多次嘗試的最終結果。移除 GIL 將解放多線程性能,使 Python 成為真正的多核語言,并顯著提高其并行工作負載的性能。
有了這項提議,現實中 Python 中對多線程和并發性的支持更上一步。
為什么要移除 Python 的 GIL?
Python 的內存管理機制通過維護每個對象的引用計數來跟蹤對象的使用情況。當某個對象的引用計數降為零時,該對象就會被移除。
由于 Python 誕生時,多處理器系統還很罕見,多核處理器也不存在,因此這種引用計數機制不能確保線程安全。
相反,Python 通過一次只允許一個線程訪問一個對象來實現線程安全。這就是 GIL 的目的。
多年來,許多項目都試圖移除 GIL。它們確實使多線程程序運行得更快,但代價是降低了單線程程序的性能。考慮到絕大多數 Python 應用程序都是單線程的,這種帶來了弊大于利的局面。過去,雖然項目維護人員一直對 GIL 進行改進,但它仍然是一個嚴重的瓶頸。
Python 的核心開發人員最終決定從 CPython 中移除 GIL,但前提是不能降低單線程程序的運行速度。
無 GIL 的 Python 將如何運行
目前此提案對無 GIL 版本的 Python 的建議使用了多種技術,最終使引用計數線程安全,而對單線程程序的速度不產生影響或影響很小。
譬如:
- 有偏差的引用計數。對僅由單線程訪問的對象進行計數的處理方式與對由多線程訪問的對象進行計數的處理方式不同(處理速度更快)。
- 永生化。有些對象(如 None)永遠不需要重新分配,因此不需要跟蹤其引用計數。
- 線程安全的內存分配。CPython 對象的新內存分配系統將使垃圾回收器更容易跟蹤對象,并以線程安全的方式分配內存。
- 延遲引用計數。某些對象(如模塊中的頂層函數)的引用計數可以安全地延遲。這既節省了時間,也節省了資源。
- 改進的垃圾回收器。CPython 垃圾收集器會清理循環對象引用,即兩個或多個對象之間的相互引用。無 GIL 版本對垃圾回收器進行了許多修改,例如刪除了用于跟蹤對象的 "生成 "系統。
如何逐步實現no-GIL Python
把 PEP 703 提案落實下來,其實需要一定的時間,甚至可能會在多年內分階段進行。在此期間,CPython 解釋器將過渡到使 no-GIL 版本首先成為可選版本,然后成為支持版本,最后成為 CPython 的標準版本。
為了實現這一目標,CPython 的開發人員將在 CPython 中添加具有實驗性的 "no-GIL "編譯模式,這樣人們就可以編譯帶有或不帶 GIL 的 CPython 版本。最終,無 GIL 編譯模式將成為默認模式。
以下是從 CPython 中移除 GIL 的計劃的展開過程。
第一步:讓無 GIL CPython 成為可選項
對于 CPython 開發者和更大的 Python 社區來說,無 GIL CPython 的第一個版本是實驗性版本。這一實驗階段有幾個目標:
- 讓 Python 社區的其他成員參與進來。Python 的任何重大改變都需要更廣泛的 Python 社區的支持。實驗性構建為 Python 用戶提供了一種安全測試其代碼的方法,并讓他們了解非線程代碼和線程代碼將如何運行。
- 為 Python 發行版提供發布 no-GIL 的選項,而不是直接要求。像 Conda 或 WinPython 這樣的 Python 發行版需要保證與現有 CPython 的兼容性。在過渡階段,它們可以提供安裝常規或無 GIL 版本 CPython 的選項。這將允許 Conda 或 WinPython 用戶選擇最符合其需求的版本。
- 確定 no-GIL 項目是否值得。如果社區大規模試用 no-GIL 構建,并對結果不滿意,CPython 核心開發人員保留退出的權利。雙重構建意味著在短期內會增加維護負擔,但如果 no-GIL 項目證明不值得,他們也有退路。
第二步:讓 No-GIL CPython 成為支持版本
下一階段將提供 no-GIL 構建,作為 CPython 受支持的替代構建。用戶可以選擇安裝 no-GIL 或 GIL 版本,其中任何一個版本都是 CPython 的正式支持版本,會收到錯誤修復、安全補丁和更新。
本階段的一個重要目標是設定一個目標日期,將 no-GIL 變為默認版本。這可能會與其他 Python 功能的廢棄和移除發生在同一時間段上——至少需要經歷兩三個版本的迭代,也就是至少兩三年的時間。
第三步:讓 no-GIL CPython 成為默認版本
最后一個階段是將 no-GIL 版本的 CPython 作為默認值,并從 CPython 中移除所有與 GIL 相關的代碼。
CPython 核心開發人員 Thomas Wouters 寫道:"我們不想在這個問題上等待太久,因為擁有兩種通用構建模式可能會給社區帶來沉重負擔(例如,這會使測試資源和調試場景增加一倍),但我們也不能操之過急。我們認為要達到這一階段可能需要長達五年的時間"。
取消 GIL 面臨的最大挑戰
盡管技術上的挑戰令人生畏,但這一計劃所面臨的最大挑戰并不僅僅是技術上的。更大的挑戰在于如何讓 Python 生態系統的其他部分與這些變化保持一致,并確保無 GIL 的 Python 不會帶來比解決更多的問題。
根據 Wouters 的說法,"......為適應 no-GIL 版本而需要對第三方代碼進行的任何修改,都應該能在有 GIL 版本中正常運行(盡管仍需要解決與舊 Python 版本的向后兼容性問題)"。
如上所述,另一個巨大的挑戰是“讓 Python 社區的其他成員也參與進來.....并確保我們希望做出的改變和我們希望他們做出的改變都能被接受”,Wouters 說道。
Wouters 表示:“在我們承諾完全轉向 no-GIL 構建之前,我們需要看到社區對它的支持。我們不能只是將其設置為默認值,然后期待社區找出他們需要做哪些工作來支持它”。
Python 社區在從 Python 2 過渡到 Python 3 的過程中經歷了巨大的成長之痛,因此任何像移除 GIL 這樣的重大改變都必須徹底向后兼容。正如 Wouters 所說,"我們不希望再出現 Python 3 的情況"。
除了危險和挑戰之外,還有一個巨大的回報:Python 最終支持了程序員在 21 世紀所期待的并行性。