# GIL ## Global Interpreter Lock 🔒 <!-- .slide: data-background="https://hackmd.io/_uploads/r1kdziX3R.jpg" --> --- ## Python Interpreter 實作 <hr/> + CPython (Python VM)<!-- .element: class="fragment" data-fragment-index="1" --> + Jython (JVM)<!-- .element: class="fragment" data-fragment-index="2" --> + IronPython (CLR)<!-- .element: class="fragment" data-fragment-index="3" --> + PyPy (JIT)<!-- .element: class="fragment" data-fragment-index="4" --> --- ## Python Interpreter 實作 <hr/> + CPython (Python VM)<!-- .element: class="fragment highlight-green" data-fragment-index="1" --> + Jython (JVM) + IronPython (CLR) + PyPy (JIT)<!-- .element: class="fragment highlight-green" data-fragment-index="2" --> --- ## C 源代碼中的 critical section <hr/> 在 CPython 實作中 每個 Python 物件都是一個 [PyObject] 每個 [PyObject] 都有一個引用計數 (reference count) [PyObject]: https://github.com/python/cpython/blob/e0264a61119d551658d9445af38323ba94fc16db/Include/object.h#L105 --- ## C 源代碼中的 critical section <hr/> 如果一個新的變數引用到物件 `refcount += 1` 否則引用計數 `refcount -= 1` <font color="#d29922">⚠️ Warning</font><!-- .element: class="fragment" data-fragment-index="1" --> 這裡遞增遞減是 critical section<!-- .element: class="fragment" data-fragment-index="1" --> 卻非 atomic operation<!-- .element: class="fragment" data-fragment-index="1" --> 在多線程情況下,就很可能發生 race condition<!-- .element: class="fragment" data-fragment-index="1" --> --- ## C 源代碼中的 critical section <hr/> 不僅此處 CPython 在 [PyObject] 的其他相關實作中 還有非常多處類似這樣的 critical section <font color="#3fb950">📗 Solution</font><!-- .element: class="fragment" data-fragment-index="1" --> 有 critical section 的地方就設鎖<!-- .element: class="fragment" data-fragment-index="1" --> (嗎?)<!-- .element: class="fragment" data-fragment-index="2" --> [PyObject]: https://github.com/python/cpython/blob/e0264a61119d551658d9445af38323ba94fc16db/Include/object.h#L105 --- ## C 源代碼中的 critical section <hr/> 假設 CPython 的實作擁有各種繁雜或大或小的鎖... + 很多鎖就可能產生 deadlock 問題<!-- .element: class="fragment" data-fragment-index="1" --> + 更別提很多鎖超難維護和迭代<!-- .element: class="fragment" data-fragment-index="2" --> + 變相讓 C extension 開發者髮際線後退<!-- .element: class="fragment" data-fragment-index="3" --> <font color="#2f81f7">📘 Note</font><!-- .element: class="fragment" data-fragment-index="4" --> Python 之所以大受歡迎<!-- .element: class="fragment" data-fragment-index="4" --> 一部分要歸功於它的 C Extension 容易開發!<!-- .element: class="fragment" data-fragment-index="4" --> --- ## 辣格男人 <hr/> <div class="r-stack"> <img class="fragment fade-out" data-fragment-index="1" src="https://hackmd.io/_uploads/rkc6e9X2A.jpg"> <img class="fragment" data-fragment-index="1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Guido_van_Rossum_OSCON_2006.jpg/220px-Guido_van_Rossum_OSCON_2006.jpg"> </div> Guido van Rossum<!-- .element: class="fragment fade-in" data-fragment-index="2" --> 我是誰<!-- .element: class="fragment fade-out" data-fragment-index="1" --> --- ## 每一個 bytecode 都是 thread-safe <hr/> Guido 果斷上了一個全域直譯器鎖 (GIL) 其確保 interpreter 運行一個 bytecode 時 無法被其他線程打斷 (這意味著每個時間點上只能有一個線程在執行) <font color="#2f81f7">📘 Note</font><!-- .element: class="fragment" data-fragment-index="1" --> 在這麼大的 project 裡<!-- .element: class="fragment" data-fragment-index="1" --> 有時候簡單的設計真的很重要<!-- .element: class="fragment" data-fragment-index="1" --> --- ## 每一個 bytecode 都是 thread-safe <hr/> 一個 bytecode 內會運行的 C 程序 都不用擔心 race condition 的問題 即確保每一個 bytecode 都是 thread-safe 這樣的 coarse-grained locking 簡單且有效 --- ## 每個時間點上只能有一個線程在執行 <hr/> ![threading](https://hackmd.io/_uploads/rk_479mh0.png) 線程最大的優勢就是 parallelism 結果硬是被搞成 concurrency ... 而且還是在兩個 core 上面跑 這也註定 CPython 實作的 Python interperter 無法使用多線程去榨乾多核性能 --- ## 每個時間點上只能有一個線程在執行 <hr/> ![threading](https://hackmd.io/_uploads/rk_479mh0.png) 當初 CPython 在實作時是 1990 年代 那時 CPU 沒有「多核」這種東西 多線程都是跑在單核上的 因此 Guido 壓根沒想過 多線程跑在多核的 parallelism 問題 --- ## Python 線程切換時機 <hr/> CPython interpreter 在 [main loop] 中 每次執行 bytecode 時,都會觸發 GIL 的釋放檢查 <font color="#474949">🪧 P.S.<!-- .element: class="fragment fade-in" data-fragment-index="1" --> 以往大部分 bytecode 都會觸發 GIL 的釋放檢查<!-- .element: class="fragment fade-in" data-fragment-index="1" --> 但為了加速 Python 運行效率<!-- .element: class="fragment fade-in" data-fragment-index="1" --> Python 3.10 之後只有少部分 bytecode<!-- .element: class="fragment fade-in" data-fragment-index="1" --> 如條件分支、函數調用會觸發 GIL 的釋放檢查<!-- .element: class="fragment fade-in" data-fragment-index="1" --> (其餘 bytecode 會「跳過」檢查)</font><!-- .element: class="fragment fade-in" data-fragment-index="1" --> [main loop]: https://github.com/python/cpython/blob/e0264a61119d551658d9445af38323ba94fc16db/Python/ceval.c#L1739 --- ## 即便有 GIL 不代表必然 thread-safe <hr/> 因為一個操作有可能對應多個 bytecode 比如下方 += 的例子,一旦線程切換 發生在 `LOAD_NAME` 到 `BINARY_OP` 間 就會導致 race condition 所以說,你還是要自己設 lock 的 ![bytecode](https://hackmd.io/_uploads/rJvzS5m20.png) --- ## 既然還是要 lock,那 GIL 的用處? <hr/> GIL 是保護在 C level 上工作的 CPython interpreter<!-- .element: class="fragment highlight-red" data-fragment-index="1" --> (確保單個 bytecode 內是 thread-safe 的) 而不是保護在 Python level 上編程的 programmer<!-- .element: class="fragment highlight-red" data-fragment-index="2" --> 在 Python level 上,你還是要使用 lock 避免跨 bytecode 可能造成的 race condition --- ## fine-grained locking 沒有速度優勢! ||| |:---|:---| |In <font color="#a371f7">C++</font> :<br/>程式設計師需要盡力的將 shared data 上鎖的時間縮的愈短愈好,來<font color="#a371f7">縮小 critical section</font> 以及允許更多的 parallelism|In <font color="#3fb950">Python</font> :<br/>線程不能 parallelism,因此 fine-grained locking 並沒有優勢。只要沒有線程會在睡眠時持有鎖,做些 I/O 或是其他會釋放 GIL 的操作,你就應該<font color="#3fb950">使用最粗糙,最簡單的鎖</font>| --- ## 如果你需要的是 parallelism 的解決方案 <hr/> 1. 使用 multiprocessing 避開 GIL 2. 自己開發 C extension,在 C 裡面開多線程 然後自己設法解決 race condition 的問題 3. 別用 CPython<br/>(噢,不可能,CPython 生態系太完善了) --- ## Gilectomy <hr/> 實際上從 Python 3.13 開始 Python core dev 已經接受 GIL 切除提案了 總之現在的 Python 會一步一步朝向 no-GIL 前進 (期間 GIL 仍會是預設選項) --- ## Gilectomy <hr/> 這過程一定會造成某種程度的破壞 (因為太多 third party library 仰賴 GIL 了) 但他們的目標是盡量做到向下兼容 (當初 Python 2 轉 3 沒向下兼容被一堆人罵爆) --- ## Gilectomy <hr/> 他們估計要完全 no-GIL + 生態系支持 可能要花 5 年以上的時間 --- ## Gilectomy <hr/> 當然 如果 GIL 能完好地被移除 且甚至帶來性能提升 這當然是最好的 --- ## Gilectomy <hr/> 等到那一天 Python 將真正擁有 multithreading parallelism ![blackcat](https://hackmd.io/_uploads/BJ96oT4hC.png) --- ## That's a wrap!
{"title":"GIL","showTags":"true","description":"Global Interpreter Lock 🔒","image":"https://hackmd.io/_uploads/Bk8SWjXnR.jpg","lang":"zh-TW","contributors":"[{\"id\":\"dd7526e7-48fc-4add-bf34-cea1de2571e5\",\"add\":10151,\"del\":3843}]"}
    258 views