1. 程式人生 > >python3.5全域性直譯器鎖GIL-實現原理淺析

python3.5全域性直譯器鎖GIL-實現原理淺析

python3全域性直譯器鎖淺談

本文環境python3.5.2。

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

定義如上,這就是python中的全域性直譯器鎖,即在同一個Python程序中,在開啟多執行緒的情況下,同一時刻只能有一個執行緒執行,因為cpython的記憶體管理不是執行緒安全,這樣就導致了在現在多核處理器上,一個Python程序無法充分利用處理器的多核處理。在Python的多執行緒執行環境中,每個執行緒都需要去先爭取GIL鎖,等到獲取鎖之後,才能繼續執行,隨著python的發展,GIL的鎖的競爭方式也隨之發生了相應變化。在Python2中,虛擬機器執行位元組碼是,通過計數計算的位元組碼指令條數,當執行位元組碼條數到達100條時,就放棄執行讓其他執行緒去執行,而此時喚醒等待中的哪個執行緒執行則完全依賴於作業系統的排程,等執行緒喚醒後則繼續執行100條指令後,然後放棄執行,依次迴圈;而從Python3.2之後,優化了爭取GIL鎖相關的內容,GIL的爭取就主要利用執行緒間共享的全域性變數進行同步獲取GIL鎖,使用的實現方式,已linux的pthread執行緒庫為例,主要是利用了條件變數讓執行緒間共享的全域性變數進行同步,以此來達到獲取和放棄GIL,其中等待的執行緒預設情況下在等待5000微妙後,當前正在執行的執行緒沒有主動放棄GIL鎖,則通過設定全域性共享值,讓正在執行的執行緒檢測到需要讓出GIL後則讓出GIL鎖,等待的執行緒則執行。

python3全域性直譯器鎖實現小探

多執行緒的例子基於pthread實現,在執行啟動多執行緒的python指令碼時,在主執行緒執行時,會執行到PyEval_EvalFrameEx位元組碼直譯器函式,該函式中有個for迴圈一直執行解析出來的位元組碼,

PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
    ...
    for (;;) {
        ...
    if (_Py_atomic_load_relaxed(&eval_breaker)) {
        if (*next_instr == SETUP_FINALLY) {
            /* Make the last opcode before
               a try: finally: block uninterruptible. */
            goto fast_next_opcode;
        }
    #ifdef WITH_TSC
        ticked = 1;
    #endif
        if (_Py_atomic_load_relaxed(&pendingcalls_to_do)) {
            if (Py_MakePendingCalls() < 0)
                goto error;
        }
    #ifdef WITH_THREAD
        if (_Py_atomic_load_relaxed(&gil_drop_request)) {
            /* Give another thread a chance */
            if (PyThreadState_Swap(NULL) != tstate)
                Py_FatalError("ceval: tstate mix-up");
            drop_gil(tstate);

            /* Other threads may run now */

            take_gil(tstate);

            /* Check if we should make a quick exit. */
            if (_Py_Finalizing && _Py_Finalizing != tstate) {
                drop_gil(tstate);
                PyThread_exit_thread();
            }

            if (PyThreadState_Swap(tstate) != NULL)
                Py_FatalError("ceval: orphan tstate");
        }
#endif
        /* Check for asynchronous exceptions. */
        if (tstate->async_exc != NULL) {
            PyObject *exc = tstate->async_exc;
            tstate->async_exc = NULL;
            UNSIGNAL_ASYNC_EXC();
            PyErr_SetNone(exc);
            Py_DECREF(exc);
            goto error;
        }
    }
     ...
}

在for迴圈執行位元組碼的時候,每執行一次操作碼都會檢查eval_breaker和gil_drop_request兩個值,這兩個值就是GIL中申請和釋放的全域性變數。

當子執行緒還沒有開始執行的時候,此時主執行緒每次執行到這個函式檢查時,都不符合條件,eval_breaker和gil_drop_request預設為0,此時就繼續執行。當子執行緒呼叫了Modules/_threadmodule.c中的

static PyObject *
thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
{
    ...
    PyEval_InitThreads(); /* Start the interpreter's thread-awareness */
    ident = PyThread_start_new_thread(t_bootstrap, (void*) boot);
    ...
}

此時開始新執行緒的時候呼叫了PyEval_InitThreads函式,

void
PyEval_InitThreads(void)
{
    if (gil_created())
        return;
    create_gil();
    take_gil(PyThreadState_GET());
    main_thread = PyThread_get_thread_ident();
    if (!pending_lock)
        pending_lock = PyThread_allocate_lock();
}

此時就會呼叫了gil_created檢查是否建立了gil鎖,create_gil初始化相應鎖,因為基於pthread執行緒庫實現執行緒,因為用了pthread的執行緒訊號同步,同步的時候需要上鎖,所以該函式就是給相關變數上鎖,然後呼叫take_gil函式去獲取gil鎖,

static void take_gil(PyThreadState *tstate)
{
    int err;
    if (tstate == NULL)
        Py_FatalError("take_gil: NULL tstate");

    err = errno;
    MUTEX_LOCK(gil_mutex);                                  // 加鎖

    if (!_Py_atomic_load_relaxed(&gil_locked))              // 檢查鎖,如果此時鎖被釋放了則直接獲取鎖        
        goto _ready;

    while (_Py_atomic_load_relaxed(&gil_locked)) {          // 檢查鎖是否被鎖住
        int timed_out = 0;
        unsigned long saved_switchnum;

        saved_switchnum = gil_switch_number;
        COND_TIMED_WAIT(gil_cond, gil_mutex, INTERVAL, timed_out);          // 利用訊號等待INTERVAL時候後,返回相關結果
        /* If we timed out and no switch occurred in the meantime, it is time
           to ask the GIL-holding thread to drop it. */
        if (timed_out &&
            _Py_atomic_load_relaxed(&gil_locked) &&
            gil_switch_number == saved_switchnum) {                         // 如果time_out為1並且鎖沒有被釋放
            SET_GIL_DROP_REQUEST();                                         // 設定全域性值讓當前執行的執行緒釋放鎖
        }
    }
_ready:
#ifdef FORCE_SWITCHING
    /* This mutex must be taken before modifying gil_last_holder (see drop_gil()). */
    MUTEX_LOCK(switch_mutex);
#endif
    /* We now hold the GIL */
    _Py_atomic_store_relaxed(&gil_locked, 1);                               // 設定獲取鎖
    _Py_ANNOTATE_RWLOCK_ACQUIRED(&gil_locked, /*is_write=*/1);              

    if (tstate != (PyThreadState*)_Py_atomic_load_relaxed(&gil_last_holder)) {
        _Py_atomic_store_relaxed(&gil_last_holder, (Py_uintptr_t)tstate);
        ++gil_switch_number;
    }

#ifdef FORCE_SWITCHING
    COND_SIGNAL(switch_cond);
    MUTEX_UNLOCK(switch_mutex);
#endif
    if (_Py_atomic_load_relaxed(&gil_drop_request)) {                       // 因為獲取執行的時候需要重置
        RESET_GIL_DROP_REQUEST();                                           // 如果gil_drop_request為1則重置
    }
    if (tstate->async_exc != NULL) {
        _PyEval_SignalAsyncExc();
    }

    MUTEX_UNLOCK(gil_mutex);                                                // 解鎖互斥
    errno = err;
}

從該函式看出,當執行到while迴圈時,則檢查是否上鎖了,上鎖後則呼叫了COND_TIMED_WAIT來讓該執行緒等待一段時候後,如果在超時之前就獲取條件變數則該等待執行緒被喚醒,此時gil_locked就被釋放,此時就直接繼續執行,如果等待指定時間後,等待超時,此時就呼叫SET_GIL_DROP_REQUEST設定eval_breaker和gil_drop_request為1,讓正在執行的執行緒在執行for迴圈時檢查到需要放棄當前gil鎖了,此時等待超時的執行緒就會獲取gil鎖並獲得執行機會。

COND_TIMED_WAIT內容如下,

#define COND_TIMED_WAIT(cond, mut, microseconds, timeout_result) \
{ \
    int r = PyCOND_TIMEDWAIT(&(cond), &(mut), (microseconds)); \
    if (r < 0) \
        Py_FatalError("PyCOND_WAIT(" #cond ") failed"); \
    if (r) /* 1 == timeout, 2 == impl. can't say, so assume timeout */ \
        timeout_result = 1; \
    else \
        timeout_result = 0; \
} \

呼叫了PyCOND_TIMEDWAIT巨集定義,

/* return 0 for success, 1 on timeout, -1 on error */
Py_LOCAL_INLINE(int)
PyCOND_TIMEDWAIT(PyCOND_T *cond, PyMUTEX_T *mut, PY_LONG_LONG us)
{
    int r;
    struct timespec ts;
    struct timeval deadline;

    PyCOND_GETTIMEOFDAY(&deadline);
    PyCOND_ADD_MICROSECONDS(deadline, us);
    ts.tv_sec = deadline.tv_sec;
    ts.tv_nsec = deadline.tv_usec * 1000;

    r = pthread_cond_timedwait((cond), (mut), &ts);
    if (r == ETIMEDOUT)
        return 1;
    else if (r)
        return -1;
    else
        return 0;
}

從中可知,呼叫了pthread_cond_timedwait函式來檢查執行緒等待一定時間後,等待的執行緒會被喚醒,或者在等待的時候就會通過cond等待執行緒會被喚醒。

所有一切迷霧都在此揭開,所有的操作都是圍繞pthread中的pthread_cond_timedwait的使用來實現每個執行緒如果沒有在等待的時候內沒有讓出GIL鎖,則強制讓出GIL。在python3.5.2中預設每個等待執行的執行緒的等待時間就是INTERVAL,即預設是5000微妙。

#define DEFAULT_INTERVAL 5000
static unsigned long gil_interval = DEFAULT_INTERVAL;
#define INTERVAL (gil_interval >= 1 ? gil_interval : 1)

有關pthread_cond_timedwait的具體使用大家可自行查閱相關文件,從create_gil到take_gil函式都是圍繞該函式來準備的相關條件

此時當等待超時時,返回的timeout_result=1,此時就會呼叫SET_GIL_DROP_REQUEST函式,設定值,此時正在執行在for的執行緒就是呼叫drop_gil(tstate)釋放gil鎖,

static void drop_gil(PyThreadState *tstate)
{
    if (!_Py_atomic_load_relaxed(&gil_locked))
        Py_FatalError("drop_gil: GIL is not locked");
    /* tstate is allowed to be NULL (early interpreter init) */
    if (tstate != NULL) {
        /* Sub-interpreter support: threads might have been switched
           under our feet using PyThreadState_Swap(). Fix the GIL last
           holder variable so that our heuristics work. */
        _Py_atomic_store_relaxed(&gil_last_holder, (Py_uintptr_t)tstate);
    }

    MUTEX_LOCK(gil_mutex);                                              // 加鎖
    _Py_ANNOTATE_RWLOCK_RELEASED(&gil_locked, /*is_write=*/1); 
    _Py_atomic_store_relaxed(&gil_locked, 0);                           // 設定鎖值
    COND_SIGNAL(gil_cond);                                              // 傳送條件訊號
    MUTEX_UNLOCK(gil_mutex);                                            // 解鎖

#ifdef FORCE_SWITCHING
    if (_Py_atomic_load_relaxed(&gil_drop_request) && tstate != NULL) {
        MUTEX_LOCK(switch_mutex);
        /* Not switched yet => wait */
        if ((PyThreadState*)_Py_atomic_load_relaxed(&gil_last_holder) == tstate) {
        RESET_GIL_DROP_REQUEST();
            /* NOTE: if COND_WAIT does not atomically start waiting when
               releasing the mutex, another thread can run through, take
               the GIL and drop it again, and reset the condition
               before we even had a chance to wait for it. */
            COND_WAIT(switch_cond, switch_mutex);
    }
        MUTEX_UNLOCK(switch_mutex);
    }
#endif

}

此時就釋放了鎖,然後接著就繼續執行take_gil等待著下一次被喚醒呼叫,至此就實現了多執行緒的執行排程,就看誰先獲取鎖,獲得鎖的在等待後就會被喚醒,python的多執行緒執行的排程基本思路如上所述,如有疏漏請批評指正。

總結

在Python3.2之後,有關GIL鎖的最大的改變就是利用了作業系統提供的執行緒資料同步喚醒的機制,實現了每個執行緒的排程執行,並且每個執行緒獲取GIL鎖的執行時間大致在5000微妙,改變了以往通過執行位元組碼執行計數的執行緒排程方式,具體的Python實現的程式碼機制可參考pthread中有關pthread_cond_timedwait的示例程式碼,所有的加鎖傳送訊號的操作都是圍繞該函式的使用而來,本文只是簡單的分析了GIL的相關流程與原理,如有錯誤請批評指正。

GIL相關內容連結:

相關推薦

python3.5全域性直譯器GIL-實現原理淺析

python3全域性直譯器鎖淺談 本文環境python3.5.2。 python全域性直譯器鎖 In CPython, the global interpreter lock, or GIL, is a mutex that prevents mul

全域性直譯器GIL

解釋一下對GIL的理解?   GIL 又叫全域性直譯器鎖,首先說一點,Python語言與GIL全域性直譯器鎖沒有關係,僅僅是因為歷史原因,在cpython直譯器中還存在GIL難以移除。GIL是功能與效能權衡後的產物,它有著存在的合理性,也有著難以移除的歷史客觀因素。   為什麼存在GIL?

python 全域性直譯器(GIL)的問題

GIL即全域性直譯器鎖,是屬於直譯器層面的互斥鎖,確切的說是CPython直譯器內部的一把鎖。GIL是為了鎖定整個直譯器內部的全域性資源,每個執行緒想要執行首先獲取GIL,而GIL本身又是一把互斥鎖,造成所有執行緒只能一個一個併發交替的執行。 GIL產生的背景 在CPyt

Python 多執行緒 多程序 全域性直譯器GIL join

Python 程式碼的執行由Python 虛擬機器(也叫直譯器主迴圈)來控制。Python 在設計之初就考慮到要在主迴圈中,同時只有一個執行緒在執行,就像單CPU 的系統中執行多個程序那樣,記憶體中可以存放多個程式,但任意時刻,只有一個程式在CPU 中執行。同樣地,雖然Py

python多執行緒和GIL全域性直譯器

1、執行緒     執行緒被稱為輕量級程序,是最小執行單元,系統排程的單位。執行緒切換需要的資源一般,效率一般。  2、多執行緒         在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒 3、

GIL全域性直譯器

併發:進行交替處理多件事情。 並行:多個cpu同時處理多個事,只在多核上能實現。  GIL是全域性解析器鎖,保證同一時刻只有一個執行緒可以使用cpu,讓我們的多執行緒沒辦法真正實現並行。 在一個程序中只有一個GIL鎖,那個執行緒拿到GIL就可以使用cpu 多個程序有多

關於python的GIL全域性直譯器的簡單理解

GIL是直譯器內部的一把鎖,確切一點說是CPython直譯器內部的一把鎖,所以要注意區分 這和我們在Python程式碼中使用執行緒鎖Lock並不是一個層面的概念。 1. GIL產生的背景: 在CPython解釋內部執行多個執行緒的時候,每個執行緒都需要直譯器內部申請相應

GIL(全域性直譯器)

1. 單執行緒死迴圈在VMware虛擬軟體中將Ubuntu設定為單核cpu# 主執行緒死迴圈,佔滿cpu while True: pass 2. 多執行緒死迴圈在VMware虛擬軟體中將Ubuntu設定為雙核cpuimport threading #子執行緒死迴圈

python GIL全域性直譯器的理解

GIL的全稱是:Global Interpreter Lock,意思就是全域性直譯器鎖,這個GIL並不是python的特性,他是隻在Cpython直譯器裡引入的一個概念,而在其他的語言編寫的直譯器裡就沒有這個GIL例如:Jython,Pypy為什麼會有gil?:       

Python 執行速度慢原因之一一GIL全域性直譯器)視覺化

因為它是GIL(全域性直譯器鎖) 現代計算機的 CPU 有多個核心,有時甚至有多個處理器。為了利用所有計算能力,作業系統定義了一個底層結構,叫做執行緒,而一個程序(例如 Chrome瀏覽器)能夠生成多個執行緒,通過執行緒來執行系統指令。這樣如果一個程序是要使用很多 CPU,

網路程式設計之多執行緒——GIL全域性直譯器

網路程式設計之多執行緒——GIL全域性直譯器鎖 一、引子 定義: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python b

自旋,讀寫和順序實現原理

並且 保護 表達 min 返回 create creat rwlock ini 常用的同步原語鎖,到多核處理器時代鎖已經是必不可少的同步方式之一了。無論設計多優秀的多線程數據結構,都避不開有競爭的臨界區,此時高效的鎖顯得至關重要。鎖的顆粒度是框架/程序設計者所關註的,

mysql + Python3.5.2 + Django + Uwsgi + Nginx實現生產環境

ast 配置 static var pst ads sgi 服務 關閉進程 官方文檔:http://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/tutorials/Django_and_nginx.html前面已經安裝好mysql數據

Java樂觀實現原理(案例)

extends 默認 tomat 讀取數據 pac creat fifo for ava 簡要說明: 表設計時,需要往表裏加一個version字段。每次查詢時,查出帶有version的數據記錄,更新數據時,判斷數據庫裏對應id的記錄的version是否和查出的version

拜託,面試請不要再問我Redis分散式實現原理!【石杉的架構筆記】

歡迎關注個人公眾號:石杉的架構筆記(ID:shishan100) 週一至五早8點半!精品技術文章準時送上! 目錄 一、寫在前面 二、Redisson實現Redis分散式鎖的底層原理       (1)加鎖機制       (2)鎖互斥機制  

拜託,面試請不要再問我Redis分散式實現原理

目錄 一、寫在前面 二、Redisson實現Redis分散式鎖的底層原理       (1)加鎖機制       (2)鎖互斥機制       (3)watch dog自動延期機制   &nbs

Redis分散式實現原理看這篇就夠了~

  一、寫在前面 現在面試,一般都會聊聊分散式系統這塊的東西。通常面試官都會從服務框架(Spring Cloud、Dubbo)聊起,一路聊到分散式事務、分散式鎖、ZooKeeper等知識。 所以咱們這篇文章就來聊聊分散式鎖這塊知識,具體的來看看Redis分散式鎖的實現原理

Redis分布式實現原理

文章 延期 abc 自己 ash str 中一 aaaaa 默認 一、寫在前面 現在面試,一般都會聊聊分布式系統這塊的東西。通常面試官都會從服務框架(Spring Cloud、Dubbo)聊起,一路聊到分布式事務、分布式鎖、ZooKeeper等知識。 所以咱們這篇文

七張圖徹底講清楚ZooKeeper分散式實現原理【石杉的架構筆記】

歡迎關注個人公眾號:石杉的架構筆記(ID:shishan100) 週一至週五早8點半!精品技術文章準時送上! 一、寫在前面 之前寫過一篇文章(《拜託,面試請不要再問我Redis分散式鎖的實現原理》),給大家說了一下Redisson這個開源框架是如何實現Redis分散式鎖原理的,這篇文章再給大家聊一下ZooKe

Java架構-拜託,面試請不要再問我Redis分散式實現原理

一、寫在前面 現在面試,一般都會聊聊分散式系統這塊的東西。通常面試官都會從服務框架(Spring Cloud、Dubbo)聊起,一路聊到分散式事務、分散式鎖、ZooKeeper等知識。 所以咱們這篇文章就來聊聊分散式鎖這塊知識,具體的來看看Redis分散式鎖的實現原理。 說實