最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

對Python中GIL(全局解釋器鎖)的一點(diǎn)理解淺析

 更新時(shí)間:2022年05月29日 08:44:12   作者:zikcheng  
首先需要明確的一點(diǎn)是GIL并不是Python的特性,它是在實(shí)現(xiàn)Python解析器(CPython)時(shí)所引入的一個(gè)概念,下面這篇文章主要給大家介紹了關(guān)于對Python中GIL的一點(diǎn)理解,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

GIL(Global Interpreter Lock),全局解釋器鎖,是 CPython 為了避免在多線程環(huán)境下造成 Python 解釋器內(nèi)部數(shù)據(jù)的不一致而引入的一把鎖,讓 Python 中的多個(gè)線程交替運(yùn)行,避免競爭。

需要說明的是 GIL 不是 Python 語言規(guī)范的一部分,只是由于 CPython 實(shí)現(xiàn)的需要而引入的,其他的實(shí)現(xiàn)如 Jython 和 PyPy 是沒有 GIL 的。那么為什么 CPython 需要 GIL 呢,下面我們就來一探究竟(基于 CPython 3.10.4)。

為什么需要 GIL

GIL 本質(zhì)上是一把鎖,學(xué)過操作系統(tǒng)的同學(xué)都知道鎖的引入是為了避免并發(fā)訪問造成數(shù)據(jù)的不一致。CPython 中有很多定義在函數(shù)外面的全局變量,比如內(nèi)存管理中的 usable_arenas 和 usedpools,如果多個(gè)線程同時(shí)申請內(nèi)存就可能同時(shí)修改這些變量,造成數(shù)據(jù)錯(cuò)亂。另外 Python 的垃圾回收機(jī)制是基于引用計(jì)數(shù)的,所有對象都有一個(gè) ob_refcnt字段表示當(dāng)前有多少變量會(huì)引用當(dāng)前對象,變量賦值、參數(shù)傳遞等操作都會(huì)增加引用計(jì)數(shù),退出作用域或函數(shù)返回會(huì)減少引用計(jì)數(shù)。同樣地,如果有多個(gè)線程同時(shí)修改同一個(gè)對象的引用計(jì)數(shù),就有可能使 ob_refcnt 與真實(shí)值不同,可能會(huì)造成內(nèi)存泄漏,不會(huì)被使用的對象得不到回收,更嚴(yán)重可能會(huì)回收還在被引用的對象,造成 Python 解釋器崩潰。

GIL 的實(shí)現(xiàn)

CPython 中 GIL 的定義如下

struct _gil_runtime_state {
    unsigned long interval; // 請求 GIL 的線程在 interval 毫秒后還沒成功,就會(huì)向持有 GIL 的線程發(fā)出釋放信號
    _Py_atomic_address last_holder; // GIL 上一次的持有線程,強(qiáng)制切換線程時(shí)會(huì)用到
    _Py_atomic_int locked; // GIL 是否被某個(gè)線程持有
    unsigned long switch_number; // GIL 的持有線程切換了多少次
    // 條件變量和互斥鎖,一般都是成對出現(xiàn)
    PyCOND_T cond;
    PyMUTEX_T mutex;
    // 條件變量,用于強(qiáng)制切換線程
    PyCOND_T switch_cond;
    PyMUTEX_T switch_mutex;
};

最本質(zhì)的是 mutex 保護(hù)的 locked 字段,表示 GIL 當(dāng)前是否被持有,其他字段是為了優(yōu)化 GIL 而被用到的。線程申請 GIL 時(shí)會(huì)調(diào)用 take_gil() 方法,釋放 GIL時(shí) 調(diào)用 drop_gil() 方法。為了避免饑餓現(xiàn)象,當(dāng)一個(gè)線程等待了 interval 毫秒(默認(rèn)是 5 毫秒)還沒申請到 GIL 的時(shí)候,就會(huì)主動(dòng)向持有 GIL 的線程發(fā)出信號,GIL 的持有者會(huì)在恰當(dāng)時(shí)機(jī)檢查該信號,如果發(fā)現(xiàn)有其他線程在申請就會(huì)強(qiáng)制釋放 GIL。這里所說的恰當(dāng)時(shí)機(jī)在不同版本中有所不同,早期是每執(zhí)行 100 條指令會(huì)檢查一次,在 Python 3.10.4 中是在條件語句結(jié)束、循環(huán)語句的每次循環(huán)體結(jié)束以及函數(shù)調(diào)用結(jié)束的時(shí)候才會(huì)去檢查。

申請 GIL 的函數(shù) take_gil() 簡化后如下

static void take_gil(PyThreadState *tstate)
{
    ...
    // 申請互斥鎖
    MUTEX_LOCK(gil->mutex);
    // 如果 GIL 空閑就直接獲取
    if (!_Py_atomic_load_relaxed(&gil->locked)) {
        goto _ready;
    }
    // 嘗試等待
    while (_Py_atomic_load_relaxed(&gil->locked)) {
        unsigned long saved_switchnum = gil->switch_number;
        unsigned long interval = (gil->interval >= 1 ? gil->interval : 1);
        int timed_out = 0;
        COND_TIMED_WAIT(gil->cond, gil->mutex, interval, timed_out);
        if (timed_out &&  _Py_atomic_load_relaxed(&gil->locked) && gil->switch_number == saved_switchnum) {
            SET_GIL_DROP_REQUEST(interp);
        }
    }
_ready:
    MUTEX_LOCK(gil->switch_mutex);
    _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, (uintptr_t)tstate);
        ++gil->switch_number;
    }
    // 喚醒強(qiáng)制切換的線程主動(dòng)等待的條件變量
    COND_SIGNAL(gil->switch_cond);
    MUTEX_UNLOCK(gil->switch_mutex);
    if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request)) {
        RESET_GIL_DROP_REQUEST(interp);
    }
    else {
        COMPUTE_EVAL_BREAKER(interp, ceval, ceval2);
    }
    ...
    // 釋放互斥鎖
    MUTEX_UNLOCK(gil->mutex);
}

整個(gè)函數(shù)體為了保證原子性,需要在開頭和結(jié)尾分別申請和釋放互斥鎖 gil->mutex。如果當(dāng)前 GIL 是空閑狀態(tài)就直接獲取 GIL,如果不空閑就等待條件變量 gil->cond interval 毫秒(不小于 1 毫秒),如果超時(shí)并且期間沒有發(fā)生過 GIL 切換就將 gil_drop_request 置位,請求強(qiáng)制切換 GIL 持有線程,否則繼續(xù)等待。一旦獲取 GIL 成功需要更新 gil->locked、gil->last_holder 和 gil->switch_number 的值,喚醒條件變量 gil->switch_cond,并且釋放互斥鎖 gil->mutex。

釋放 GIL 的函數(shù) drop_gil() 簡化后如下

static void drop_gil(struct _ceval_runtime_state *ceval, struct _ceval_state *ceval2,
         PyThreadState *tstate)
{
    ...
    if (tstate != NULL) {
        _Py_atomic_store_relaxed(&gil->last_holder, (uintptr_t)tstate);
    }
    MUTEX_LOCK(gil->mutex);
    _Py_ANNOTATE_RWLOCK_RELEASED(&gil->locked, /*is_write=*/1);
    // 釋放 GIL
    _Py_atomic_store_relaxed(&gil->locked, 0);
    // 喚醒正在等待 GIL 的線程
    COND_SIGNAL(gil->cond);
    MUTEX_UNLOCK(gil->mutex);
    if (_Py_atomic_load_relaxed(&ceval2->gil_drop_request) && tstate != NULL) {
        MUTEX_LOCK(gil->switch_mutex);
        // 強(qiáng)制等待一次線程切換才被喚醒,避免饑餓
        if (((PyThreadState*)_Py_atomic_load_relaxed(&gil->last_holder)) == tstate)
        {
            assert(is_tstate_valid(tstate));
            RESET_GIL_DROP_REQUEST(tstate->interp);
            COND_WAIT(gil->switch_cond, gil->switch_mutex);
        }
        MUTEX_UNLOCK(gil->switch_mutex);
    }
}

首先在 gil->mutex 的保護(hù)下釋放 GIL,然后喚醒其他正在等待 GIL 的線程。在多 CPU 的環(huán)境下,當(dāng)前線程在釋放 GIL 后有更高的概率重新獲得 GIL,為了避免對其他線程造成饑餓,當(dāng)前線程需要強(qiáng)制等待條件變量 gil->switch_cond,只有在其他線程獲取 GIL 的時(shí)候當(dāng)前線程才會(huì)被喚醒。

幾點(diǎn)說明

GIL 優(yōu)化

受 GIL 約束的代碼不能并行執(zhí)行,降低了整體性能,為了盡量降低性能損失,Python 在進(jìn)行 IO 操作或不涉及對象訪問的密集 CPU 計(jì)算的時(shí)候,會(huì)主動(dòng)釋放 GIL,減小了 GIL 的粒度,比如

  • 讀寫文件
  • 網(wǎng)絡(luò)訪問
  • 加密數(shù)據(jù)/壓縮數(shù)據(jù)

所以嚴(yán)格來說,在單進(jìn)程的情況下,多個(gè) Python 線程時(shí)可能同時(shí)執(zhí)行的,比如一個(gè)線程在正常運(yùn)行,另一個(gè)線程在壓縮數(shù)據(jù)。

用戶數(shù)據(jù)的一致性不能依賴 GIL

GIL 是為了維護(hù) Python 解釋器內(nèi)部變量的一致性而產(chǎn)生的鎖,用戶數(shù)據(jù)的一致性不由 GIL 負(fù)責(zé)。雖然 GIL 在一定程度上也保證了用戶數(shù)據(jù)的一致性,比如 Python 3.10.4 中不涉及跳轉(zhuǎn)和函數(shù)調(diào)用的指令都會(huì)在 GIL 的約束下原子性的執(zhí)行,但是數(shù)據(jù)在業(yè)務(wù)邏輯上的一致性需要用戶自己加鎖來保證。

下面的代碼用兩個(gè)線程模擬用戶集碎片得獎(jiǎng)

from threading import Thread

def main():
    stat = {"piece_count": 0, "reward_count": 0}
    t1 = Thread(target=process_piece, args=(stat,))
    t2 = Thread(target=process_piece, args=(stat,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(stat)

def process_piece(stat):
    for i in range(10000000):
        if stat["piece_count"] % 10 == 0:
            reward = True
        else:
            reward = False
        if reward:
            stat["reward_count"] += 1
        stat["piece_count"] += 1

if __name__ == "__main__":
    main()

假設(shè)用戶每集齊 10 個(gè)碎片就能得到一次獎(jiǎng)勵(lì),每個(gè)線程收集了 10000000 個(gè)碎片,應(yīng)該得到 9999999 個(gè)獎(jiǎng)勵(lì)(最后一次沒有計(jì)算),總共應(yīng)該收集 20000000 個(gè)碎片,得到 1999998 個(gè)獎(jiǎng)勵(lì),但是在我電腦上一次運(yùn)行結(jié)果如下

{'piece_count': 20000000, 'reward_count': 1999987}

總的碎片數(shù)量與預(yù)期一致,但是獎(jiǎng)勵(lì)數(shù)量卻少了 12 個(gè)。碎片數(shù)量正確是因?yàn)樵?Python 3.10.4 中,stat["piece_count"] += 1 是在 GIL 約束下原子性執(zhí)行的。由于每次循環(huán)結(jié)束都可能切換執(zhí)行線程,那么可能線程 t1 在某次循環(huán)結(jié)束時(shí)將 piece_count 加到 100,但是在下次循環(huán)開始模 10 判斷前,Python 解釋器切換到線程 t2 執(zhí)行,t2 將 piece_count 加到 101,那么就會(huì)錯(cuò)過一次獎(jiǎng)勵(lì)。

附:如何避免受到GIL的影響

說了那么多,如果不說解決方案就僅僅是個(gè)科普帖,然并卵。GIL這么爛,有沒有辦法繞過呢?我們來看看有哪些現(xiàn)成的方案。

用multiprocess替代Thread

multiprocess庫的出現(xiàn)很大程度上是為了彌補(bǔ)thread庫因?yàn)镚IL而低效的缺陷。它完整的復(fù)制了一套thread所提供的接口方便遷移。唯一的不同就是它使用了多進(jìn)程而不是多線程。每個(gè)進(jìn)程有自己的獨(dú)立的GIL,因此也不會(huì)出現(xiàn)進(jìn)程之間的GIL爭搶。

當(dāng)然multiprocess也不是萬能良藥。它的引入會(huì)增加程序?qū)崿F(xiàn)時(shí)線程間數(shù)據(jù)通訊和同步的困難。就拿計(jì)數(shù)器來舉例子,如果我們要多個(gè)線程累加同一個(gè)變量,對于thread來說,申明一個(gè)global變量,用thread.Lock的context包裹住三行就搞定了。而multiprocess由于進(jìn)程之間無法看到對方的數(shù)據(jù),只能通過在主線程申明一個(gè)Queue,put再get或者用share memory的方法。這個(gè)額外的實(shí)現(xiàn)成本使得本來就非常痛苦的多線程程序編碼,變得更加痛苦了。具體難點(diǎn)在哪有興趣的讀者可以擴(kuò)展閱讀這篇文章

用其他解析器

之前也提到了既然GIL只是CPython的產(chǎn)物,那么其他解析器是不是更好呢?沒錯(cuò),像JPython和IronPython這樣的解析器由于實(shí)現(xiàn)語言的特性,他們不需要GIL的幫助。然而由于用了Java/C#用于解析器實(shí)現(xiàn),他們也失去了利用社區(qū)眾多C語言模塊有用特性的機(jī)會(huì)。所以這些解析器也因此一直都比較小眾。畢竟功能和性能大家在初期都會(huì)選擇前者,Done is better than perfect。

所以沒救了么?

當(dāng)然Python社區(qū)也在非常努力的不斷改進(jìn)GIL,甚至是嘗試去除GIL。并在各個(gè)小版本中有了不少的進(jìn)步。有興趣的讀者可以擴(kuò)展閱讀這個(gè)Slide

另一個(gè)改進(jìn)Reworking the GIL

– 將切換顆粒度從基于opcode計(jì)數(shù)改成基于時(shí)間片計(jì)數(shù)

– 避免最近一次釋放GIL鎖的線程再次被立即調(diào)度

– 新增線程優(yōu)先級功能(高優(yōu)先級線程可以迫使其他線程釋放所持有的GIL鎖)

總結(jié)

GIL 是 CPython 為了在多線程環(huán)境下為了維護(hù)解釋器內(nèi)部數(shù)據(jù)一致性而引入的,為了盡可能降低 GIL 的粒度,在 IO 操作和不涉及對象訪問的 CPU 計(jì)算時(shí)會(huì)主動(dòng)釋放 GIL。最后,用戶數(shù)據(jù)的一致性不能依賴 GIL,可能需要用戶使用 Lock 或 RLock() 來保證數(shù)據(jù)的原子性訪問。

到此這篇關(guān)于對Python中GIL理解的文章就介紹到這了,更多相關(guān)Python中GIL理解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

參考文檔

相關(guān)文章

  • python列表逆序排列的4種方法

    python列表逆序排列的4種方法

    python中的列表是可以直接進(jìn)行逆序排列的,本文主要介紹了python列表逆序排列的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2023-05-05
  • Laravel框架表單驗(yàn)證格式化輸出的方法

    Laravel框架表單驗(yàn)證格式化輸出的方法

    最近在公司的項(xiàng)目開發(fā)中使用到了 laravel 框架,采用的是前后端開發(fā)的模式。這篇文章主要介紹了Laravel框架表單驗(yàn)證格式化輸出,需要的朋友可以參考下
    2019-09-09
  • PyQt5結(jié)合matplotlib繪圖的實(shí)現(xiàn)示例

    PyQt5結(jié)合matplotlib繪圖的實(shí)現(xiàn)示例

    這篇文章主要介紹了PyQt5結(jié)合matplotlib繪圖的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Python中l(wèi)ogger日志模塊詳解

    Python中l(wèi)ogger日志模塊詳解

    這篇文章主要介紹了Python中l(wèi)ogger日志模塊詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • python密碼學(xué)各種加密模塊教程

    python密碼學(xué)各種加密模塊教程

    這篇文章主要為大家介紹了python密碼學(xué)各種加密模塊教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • python通過百度地圖API獲取某地址的經(jīng)緯度詳解

    python通過百度地圖API獲取某地址的經(jīng)緯度詳解

    這篇文章主要給大家介紹了關(guān)于python通過百度地圖API獲取某地址的經(jīng)緯度的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • Python使用Spire.XLS實(shí)現(xiàn)Excel轉(zhuǎn)PDF的實(shí)踐指南

    Python使用Spire.XLS實(shí)現(xiàn)Excel轉(zhuǎn)PDF的實(shí)踐指南

    在日常辦公中,將 Excel 文件轉(zhuǎn)換為 PDF 是一個(gè)常見的需求,但是手動(dòng)操作不僅繁瑣,而且還容易出錯(cuò),下面我們就來看看如何借助 Spire.XLS for Python,用幾行代碼輕松實(shí)現(xiàn) Excel 轉(zhuǎn) PDF吧
    2025-07-07
  • MATLAB 如何求取離散點(diǎn)的曲率最大值

    MATLAB 如何求取離散點(diǎn)的曲率最大值

    這篇文章主要介紹了MATLAB 求取離散點(diǎn)的曲率最大值操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Python獲取女友聊天記錄詳細(xì)流程

    Python獲取女友聊天記錄詳細(xì)流程

    就在前段時(shí)間,在大學(xué)睡我上鋪的兄弟,在兄嘚群里討論,說:他發(fā)現(xiàn)女朋友這幾天只要下班就坐在電腦前,不是工作原因。而且只要他靠近一點(diǎn)就會(huì)立即退出頁面,所以他一直很懷疑,在群里問我們這一個(gè)個(gè)單身狗。然后就出現(xiàn)下面這一段對話
    2021-10-10
  • Python自動(dòng)化辦公之圖片轉(zhuǎn)PDF的實(shí)現(xiàn)

    Python自動(dòng)化辦公之圖片轉(zhuǎn)PDF的實(shí)現(xiàn)

    實(shí)現(xiàn)圖片轉(zhuǎn)換成PDF文檔的操作方法有很多,綜合對比以后感覺fpdf這個(gè)模塊用起來比較方便而且代碼量相當(dāng)少。所以本文將利用Python語言實(shí)現(xiàn)圖片轉(zhuǎn)PDF,感興趣的可以了解一下
    2022-04-04

最新評論

达孜县| 壤塘县| 廊坊市| 葵青区| 德江县| 法库县| 西峡县| 正安县| 横山县| 棋牌| 隆化县| 桐柏县| 安顺市| 长寿区| 秀山| 巴林左旗| 郑州市| 高州市| 崇阳县| 龙泉市| 乐亭县| 灵寿县| 西宁市| 曲靖市| 茂名市| 余干县| 锡林浩特市| 新郑市| 乌兰浩特市| 镇赉县| 岚皋县| 宁津县| 临朐县| 旺苍县| 申扎县| 民县| 香港| 岚皋县| 沈丘县| 如皋市| 县级市|