> Python 多線程由於GIL的存在,致使其速度比單線程還要慢。可是近期我發現了一個至關好用的庫,這個庫只須要增長一個修飾符就可使原生的python多線程實現真正意義上的併發。本文將和你們一塊兒回顧下GIL對於多線程的影響,以及瞭解經過一個修飾符就能夠實現和C++同樣的多線程。html
## GIL的定義
GIL的全稱是global interpreter lock,官方的定義以下:python
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)git
從官方的解釋來看,這個全局鎖是用來防止多線程同時執行底層計算代碼的。之因此這麼作,是由於底層庫Cpython,在內存管理這塊是線程不安全的。github
## GIL有好處嗎
對GIL的第一印象是這東西限制了多線程併發,對python而言是個弊大於利的存在。可是從stackoverflow上的討論來看,這個存在仍是至關有必要的。
- 增長了單線程的運行速度
- 能夠更方便地整合一些線程不安全的C語言庫到python裏面去
首先單線程的運行速度更快了,由於有這個全局鎖的存在,在執行單線程計算的時候不須要再額外增長鎖,減小了沒必要要的開支。第二個則是能夠更好地整合用C語言所寫的python庫。如今其實挺多用C語言寫好底層計算而後封裝提供python接口的,好比數據處理領域的pandas庫,人工智能領域的計算框架Tensorflow或者pytorch,他們的底層計算都是用C語言寫的。因爲這個全局鎖的存在,咱們能夠更方便(安全)地把這些C語言的計算庫整合成一個python包,對外提供python接口。shell
## GIL對性能的影響大嗎
對於須要作大量計算的任務而言,影響是至關大的。咱們先來看一段單線程代碼:安全
```python
class A(object):
def run(self):
ans = 0
for i in range(100000000):
ans += i
a = A()
for _ in range(5):
a.run()
```
以上這段代碼是跑5次計算,每次計算是從1累加到1千萬,跑這段代碼須要17.46s。
緊接着,咱們用python的多線程庫來實現一個多線程計算:多線程
```python
import threading併發
class A(object):
def run(self):
ans = 0
for i in range(100000000):
ans += i
threads = []
for _ in range(5):
a = A()
th = threading.Thread(target=a.run)
th.start()
threads.append(th)
for th in threads:
th.join()
```
這裏咱們啓動了5個線程同時計算,而後咱們又測試下時間: **41.35**秒!!!這個時候GIL的問題就體現出來了,咱們經過多線程來實現併發,結果比單線程慢了2倍多。
### 一個神奇的修飾符
話很少說,咱們先來看下代碼。如下這段代碼和上面的多線程代碼幾乎同樣。可是咱們要注意到,在類A的定義上面,咱們增長了一個修飾符*@parl.remote_class*。
```python
import threading
import parlapp
@parl.remote_class
class A(object):
def run(self):
ans = 0
for i in range(100000000):
ans += i
threads = []
parl.connect("localhost:6006")
for _ in range(5):
a = A()
th = threading.Thread(target=a.run)
th.start()
threads.append(th)
for th in threads:
th.join()
```
如今咱們來看下計算時間:**3.74秒**!!!相比於單線程的17.46s,這裏只用了接近1/5的時間(由於咱們開了5個線程)。這裏是我以爲比較神奇的地方,並無作太多的改動,只是在個人單線程類上面增長了一個修飾符,而後用原生的python多線程繼續跑代碼就變得至關快了。框架
### 完整的使用說明:
1. 安裝這個庫:
```shell
pip install --upgrade git+https://github.com/PaddlePaddle/PARL.git
```
2. 在本地經過命令啓動一個併發服務(只須要啓動一次)
```shell
xparl start --port 6006
```
3. 寫代碼的時候經過修飾符修飾你要併發的類@parl.remote。
這裏須要注意的是隻有通過這個修飾符修飾的類才能夠實現併發。
4. 在代碼最開始的時候經過parl.connect('localhost:6006')來初始化這個包。
最後貼下這個庫的使用文檔:
https://parl.readthedocs.io/en/latest/parallel_training/setup.html
源碼在這裏:
https://github.com/PaddlePaddle/PARL/tree/develop/parl/remote
後續會繼續研究源碼,看下是怎麼作到一個修飾符就能加速的。你們若是讀過了源碼能夠一塊兒討論下:)