Python GIL全局解釋器

>GIL爲何物

GIL(Global Interpreter Lock),也稱爲全局解釋器,看下官方解釋

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.)

主要意思爲:

GIL是一個互斥鎖,它防止多個線程同時執行Python字節碼。這個鎖是必要的,主要是因爲CPython的內存管理不是線程安全的

>Python與GIL

Python的GIL只是CPython(Python解釋器)的一個問題,那麼Python又有哪些解釋器,它們也存在和CPython同樣的問題嗎?那麼什麼又是解釋器呢?

什麼是解釋器

我們寫的代碼計算機是如何識別的呢,計算機也擁有和人類相同的思維和語言嗎?顯然不是的;計算機只能識別機器指令語言也就是0和1,那麼我們編寫的程序計算機是如何識別的呢?這就是解釋器的作用了,解釋器將我們編寫的Python代碼翻譯爲機器指令語言,Python解釋器本身也是個程序,它是解釋Python代碼的,叫做解釋器.

Python解釋器有哪些

  1. CPython: 官方默認版本,使用C語言開發,是Python使用最廣泛的解釋器,有GIL.
  2. IPython: IPython是基於CPython之上的交互式解釋器,其它方面和CPython相同.
  3. PyPy: PyPy採用JIT(Just In Time)也就是即時編譯編譯器,對Python代碼執行動態編譯,目的是加快執行速度,有GIL.
  4. Jython: 運行在Java平臺上的解釋器,把Python代碼編譯爲Java字節碼執行,沒有GIL.
  5. IronPython: IronPython和Jython類似,只不過IronPython是運行在微軟.Net平臺上的Python解釋器,可以直接把Python代碼編譯成.Net的字節碼,沒有GIL.

>GIL解決了Python什麼問題呢

Python內部對變量或數據對象使用了引用計數器,我們通過計算引用個數,當個數爲0時,變量或者數據對象就被自動釋放

 

In [1]: import  sys                                                  

In [2]: count_var = "test1"                                         

In [3]: sys.getrefcount(count_var)                                  
Out[3]: 2

In [4]: add_var = count_var                                         

In [5]: sys.getrefcount(add_var)                                    
Out[5]: 3

這個引用計數器需要保護,當多個線程同時修改這個值時,可能會導致內存泄漏;SO,我們使用鎖來解決這個問題,可有時會添加多個鎖來解決,這就會導致另個問題,死鎖;

爲了避免內存泄漏和死鎖問題,CPython使用了單鎖,即全局解釋器鎖(GIL),即執行Python字節碼都需要獲取GIL,這可以防止死鎖,但它有效地使任何受CPU限制的Python程序都是單線程.

>GIL對多線程Python程序的影響

程序的性能受到計算密集型(CPU)的程序限制和I/O密集型的程序限制影響,那什麼是計算密集型和I/O密集型程序呢?

計算密集型(CPU)

高度使用CPU的程序,例如: 進行數學計算,矩陣運算,搜索,圖像處理等.

I/O密集型

I/0(Input/Output)程序是進行數據傳輸,例如: 文件操作,數據庫,網絡數據等


測試下順序執行單線程和併發執行多線程的效率

- 順序執行單線程(single_thread.py)

 

import threading
import time

def test_counter():
    i = 0
    for _ in range(100000000):
        i += 1
    return True

def main():
    start_time = time.time()
    for tid in range(2):
        t1 = threading.Thread(target=test_counter)
        t1.start()
        t1.join()
    end_time = time.time()
    print("Total time:{}".format(end_time-start_time))


if __name__ == "__main__":
    main()

執行結果:

 

Total time: 11.299654722213745

- 併發執行兩個線程(multi_thread.py)

 

import threading
import time

def test_counter():
    i = 0
    for _ in range(10000000):
        i +=1
    # return True


def main():
    start_time = time.time()
    thread_array = {}
    for tid in range(2):
        t1 = threading.Thread(target=test_counter())
        t1.start()
        thread_array[tid] = t1
    for tid in range(2):
        thread_array[tid].join()

    end_time = time.time()
    print(end_time-start_time)
    print("Total time:{} ".format(end_time-start_time))


if __name__ == '__main__':
    main()

執行結果:

 

Total time:13.7098388671875

GIL對I/O綁定多線程程序的性能影響不大,因爲線程在等待I/O時共享鎖.

GIL對計算型綁定多線程程序有影響,例如: 使用線程處理部分圖像的程序,不僅會因鎖定而成爲單線程,而且還會看到執行時間的增加,這種增加是由鎖的獲取和釋放開銷的結果.

>可以去掉累贅GIL嗎

有大佬試過,只能說結果不盡人意0 .0,等待着吧

>SO,如何處理Python中的GIL

  • 計算密集型程序
    • 使用多進程(什麼是多進程呢,後續道來)
    • 使用其它語言(將計算密集程序放到其它語言中執行)
    • 替換解釋器(可以自己嘗試)
    • 等大神解決GIL0 .0
  • I/O密集型程序
    • 使用多線程
    • 使用多進程
    • 使用多進程+多線程

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章