# 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/>

線程最大的優勢就是 parallelism
結果硬是被搞成 concurrency ...
而且還是在兩個 core 上面跑
這也註定 CPython 實作的 Python interperter
無法使用多線程去榨乾多核性能
---
## 每個時間點上只能有一個線程在執行
<hr/>

當初 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 的

---
## 既然還是要 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

---
## 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}]"}