既然有GIL了那麼爲什麼在python中多線程編碼時候還需要加鎖?

GIL

由於 python 解釋器(Cpython interpreter)不是線程安全(thread-safe)的,所以 Cpython interpreter 的實現中使用了GIL(global interpreter lock)來阻止多線程同時在一個 pyobject 上操作。這裏所說的 “不是線程安全“ 是指Cpython interpreter在內存管理上不是線程安全的。比如,兩個線程同時增加一個 python object 的引用計數(內存管理用到),如果沒有GIL,那麼這個 python object 的 reference count 只會增加一次。因此,需要有這麼一個規則:只有獲得GIL的線程才能在 python object 上操作或者調用 python/c API 函數。
但是這麼以來,貌似在interpreter執行的python程序只能單線程串行執行了麼?不是,interpreter 爲了模擬併發執行(concurrency of execution),會去嘗試切換線程(詳情可參考sys.setswitchinterval()), 鎖也會在當前線程block時候(例如讀寫文件等阻塞io情況下)釋放掉,以便讓給其他線程。interpreter 也會在執行夠一定數量(通常是100)的 opcode 後釋放鎖。

例子

import threading
import time

total = 0
lock = threading.Lock()

def increment_n_times(n):
    global total
    for i in range(n):
        total += 1

def safe_increment_n_times(n):
    global total
    for i in range(n):
        lock.acquire()
        total += 1
        lock.release()

def increment_in_x_threads(x, func, n):
    threads = [threading.Thread(target=func, args=(n,)) for i in range(x)]
    global total
    total = 0
    begin = time.time()
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()
    print('finished in {}s.\ntotal: {}\nexpected: {}\ndifference: {} ({} %)'
           .format(time.time()-begin, total, n*x, n*x-total, 100-total/n/x*100))

reference

https://wiki.python.org/moin/GlobalInterpreterLock
https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock
https://stackoverflow.com/questions/40072873/why-do-we-need-locks-for-threads-if-we-have-gil

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