Python進階:GIL(全局解釋器鎖)

一個不解之謎

  一段代碼程序員

def CountDown(n):
    while n > 0:
        n -= 1

# CountDown(100000000)
#==8秒

from threading import Thread

n = 100000000

t1 = Thread(target=CountDown, args=[n // 2])
t2 = Thread(target=CountDown, args=[n // 2])
t1.start()
t2.start()
t1.join()
t2.join()
#==9s
  能夠看出,多線程並無讓上面代碼變得更快,這是Python的問題?
  Python的線程,的的確確封裝了底層操做系統線程。在Linux系統裏是Pthread(全稱爲POSIXThread),而在Windows系統裏是WindowsThread。

爲何有GIL

  GIL,是最流行的 Python 解釋器 CPython 中的一個技術術語。它的意思是全局解釋器鎖,本質上是相似操做系統的 Mutex。每個 Python 線程,在 CPython 解釋器中執行時,都會先鎖住本身的線程,阻止別的線程執行。CPython 會作一些小把戲,輪流執行 Python 線程。安全

  因此說,CPython 引進 GIL 其實主要就是這麼兩個緣由:
  一是設計者爲了規避相似於內存管理這樣的複雜的競爭風險問題(race condition);
  二是由於 CPython 大量使用 C 語言庫,但大部分 C 語言庫都不是原生線程安全的(線程安全會下降性能和增長複雜度)。

GIL 是如何工做的?

  如圖,Thread 一、二、3 輪流執行,每個線程在開始執行時,都會鎖住 GIL,以阻止別的線程執行;一樣的,每個線程執行完一段後,會釋放 GIL,以容許別的線程開始利用資源。
  
  
  CPython還有check interval機制。早期的 Python 是 100 個 ticks,大體對應了 1000 個 bytecodes;而 Python 3 之後,interval 是 15 毫秒。
for (;;) {
    if (--ticker < 0) {
        ticker = check_interval;
    
        /* Give another thread a chance */
        PyThread_release_lock(interpreter_lock);
    
        /* Other threads may run now */
    
        PyThread_acquire_lock(interpreter_lock, 1);
    }

    bytecode = *next_instr++;
    switch (bytecode) {
        /* execute the next instruction ... */ 
    }
}
  從這段代碼中,咱們能夠看到,每一個 Python 線程都會先檢查 ticker 計數。只有在 ticker 大於 0 的狀況下,線程纔會去執行本身的 bytecode。

Python 的線程安全

  GIL 的設計,主要是爲了方便 CPython 解釋器層面的編寫者,而不是 Python 應用層面的程序員。多線程

  做爲 Python 的使用者,咱們仍是須要 lock 等工具,來確保線程安全。好比下面的這個例子:工具

n = 0
lock = threading.Lock()

def foo():
    global n
    with lock:
        n += 1

如何繞過 GIL?

  事實上,不少高性能應用場景都已經有大量的 C 實現的 Python 庫,例如 NumPy 的矩陣運算,就都是經過 C 來實現的,並不受 GIL 影響。 因此,大部分應用狀況下,你並不須要過多考慮 GIL由於若是多線程計算成爲性能瓶頸,每每已經有 Python 庫來解決這個問題了。性能

  總的來講,繞過 GIL 的大體思路有這麼兩種就夠了:
  1. 繞過 CPython,使用 JPython(Java 實現的 Python 解釋器)等別的實現;
  2. 把關鍵性能代碼,放到別的語言(通常是 C++)中實現。

參考

  極客時間《Python核心技術實戰》專欄ui

相關文章
相關標籤/搜索