大師兄的Python源碼學(xué)習(xí)筆記(四十二): Python的多線程機(jī)制(四)

大師兄的Python源碼學(xué)習(xí)筆記(四十一): Python的多線程機(jī)制(三)
大師兄的Python源碼學(xué)習(xí)筆記(四十三): Python的多線程機(jī)制(五)

四、創(chuàng)建線程

2. 線程狀態(tài)保護(hù)機(jī)制
  • 我們已經(jīng)知道,在Python中每個線程都會有一個PyThreadState對象與之關(guān)聯(lián),它保存著對應(yīng)線程的狀態(tài)和獨有信息。
Include\pystate.h

typedef struct _ts {
    /* See Python/ceval.c for comments explaining most fields */

    struct _ts *prev;
    struct _ts *next;
    PyInterpreterState *interp;

    struct _frame *frame;
    int recursion_depth;
    char overflowed; /* The stack has overflowed. Allow 50 more calls
                        to handle the runtime error. */
    char recursion_critical; /* The current calls must not cause
                                a stack overflow. */
    int stackcheck_counter;

    /* 'tracing' keeps track of the execution depth when tracing/profiling.
       This is to prevent the actual trace/profile code from being recorded in
       the trace/profile. */
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    /* The exception currently being raised */
    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    /* The exception currently being handled, if no coroutines/generators
     * are present. Always last element on the stack referred to be exc_info.
     */
    _PyErr_StackItem exc_state;

    /* Pointer to the top of the stack of the exceptions currently
     * being handled */
    _PyErr_StackItem *exc_info;

    PyObject *dict;  /* Stores per-thread state */

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */
    unsigned long thread_id; /* Thread id where this tstate was created */

    int trash_delete_nesting;
    PyObject *trash_delete_later;

    /* Called when a thread state is deleted normally, but not when it
     * is destroyed after fork().
     * Pain:  to prevent rare but fatal shutdown errors (issue 18808),
     * Thread.join() must wait for the join'ed thread's tstate to be unlinked
     * from the tstate chain.  That happens at the end of a thread's life,
     * in pystate.c.
     * The obvious way doesn't quite work:  create a lock which the tstate
     * unlinking code releases, and have Thread.join() wait to acquire that
     * lock.  The problem is that we _are_ at the end of the thread's life:
     * if the thread holds the last reference to the lock, decref'ing the
     * lock will delete the lock, and that may trigger arbitrary Python code
     * if there's a weakref, with a callback, to the lock.  But by this time
     * _PyThreadState_Current is already NULL, so only the simplest of C code
     * can be allowed to run (in particular it must not be possible to
     * release the GIL).
     * So instead of holding the lock directly, the tstate holds a weakref to
     * the lock:  that's the value of on_delete_data below.  Decref'ing a
     * weakref is harmless.
     * on_delete points to _threadmodule.c's static release_sentinel() function.
     * After the tstate is unlinked, release_sentinel is called with the
     * weakref-to-lock (on_delete_data) argument, and release_sentinel releases
     * the indirectly held lock.
     */
    void (*on_delete)(void *);
    void *on_delete_data;

    int coroutine_origin_tracking_depth;

    PyObject *coroutine_wrapper;
    int in_coroutine_wrapper;

    PyObject *async_gen_firstiter;
    PyObject *async_gen_finalizer;

    PyObject *context;
    uint64_t context_ver;

    /* Unique thread state id. */
    uint64_t id;

    /* XXX signal handlers should also be here */

} PyThreadState;
  • 從結(jié)構(gòu)體代碼可以看出楼眷,PyThreadState對象中保存著當(dāng)前線程的PyFrameObject對象及線程id等信息澄干。
  • Python內(nèi)部有一套機(jī)制旺芽,用來保證進(jìn)程始終在自己的上下文環(huán)境中運行,所以需要訪問PyThreadState中的信息擅这。
  • 再觀察結(jié)構(gòu)體代碼的頭部澈魄,可以發(fā)現(xiàn)PyThreadState對象是一個鏈表結(jié)構(gòu),將所有PyThreadState對象串聯(lián)起來仲翎。
    struct _ts *prev;
    struct _ts *next;
  • Python會使用一套TSS(Thread Specific Storage)機(jī)制來管理線程信息痹扇。
Include\pythread.h

typedef struct _Py_tss_t Py_tss_t;  /* opaque */

struct _Py_tss_t {
    int _is_initialized;
    NATIVE_TSS_KEY_T _key;
};
Python\thread.c

Py_tss_t *
PyThread_tss_alloc(void)
{
    Py_tss_t *new_key = (Py_tss_t *)PyMem_RawMalloc(sizeof(Py_tss_t));
    if (new_key == NULL) {
        return NULL;
    }
    new_key->_is_initialized = 0;
    return new_key;
}
  • 同時還會創(chuàng)建一個獨立的鎖和TSSkey密鑰:
Python\pystate.c

static _PyInitError
_PyRuntimeState_Init_impl(_PyRuntimeState *runtime)
{
    memset(runtime, 0, sizeof(*runtime));

    _PyGC_Initialize(&runtime->gc);
    _PyEval_Initialize(&runtime->ceval);

    runtime->gilstate.check_enabled = 1;

    /* A TSS key must be initialized with Py_tss_NEEDS_INIT
       in accordance with the specification. */
    Py_tss_t initial = Py_tss_NEEDS_INIT;
    runtime->gilstate.autoTSSkey = initial;

    runtime->interpreters.mutex = PyThread_allocate_lock();
    if (runtime->interpreters.mutex == NULL) {
        return _Py_INIT_ERR("Can't initialize threads for interpreter");
    }
    runtime->interpreters.next_id = -1;

    return _Py_INIT_OK();
}
  • TSS有一套API用于處理線程信息。
3. 從GIL到字節(jié)碼解釋器
  • 回顧線程創(chuàng)建的過程:
Python\pystate.c

static PyThreadState *
new_threadstate(PyInterpreterState *interp, int init)
{
    PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));

    if (_PyThreadState_GetFrame == NULL)
        _PyThreadState_GetFrame = threadstate_getframe;

    if (tstate != NULL) {
       ... ...

        if (init)
            _PyThreadState_Init(tstate);

        ... ...
    }

    return tstate;
}
  • 觀察_PyThreadState_Init函數(shù):
Python\pystate.c

void
_PyThreadState_Init(PyThreadState *tstate)
{
    _PyGILState_NoteThreadState(tstate);
}
Python\pystate.c

static void
_PyGILState_NoteThreadState(PyThreadState* tstate)
{
    /* If autoTSSkey isn't initialized, this must be the very first
       threadstate created in Py_Initialize().  Don't do anything for now
       (we'll be back here when _PyGILState_Init is called). */
    if (!_PyRuntime.gilstate.autoInterpreterState)
        return;

    /* Stick the thread state for this thread in thread specific storage.

       The only situation where you can legitimately have more than one
       thread state for an OS level thread is when there are multiple
       interpreters.

       You shouldn't really be using the PyGILState_ APIs anyway (see issues
       #10915 and #15751).

       The first thread state created for that given OS level thread will
       "win", which seems reasonable behaviour.
    */
    if (PyThread_tss_get(&_PyRuntime.gilstate.autoTSSkey) == NULL) {
        if ((PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, (void *)tstate)
             ) != 0)
        {
            Py_FatalError("Couldn't create autoTSSkey mapping");
        }
    }

    /* PyGILState_Release must not try to delete this thread state. */
    tstate->gilstate_counter = 1;
}
  • _PyGILState_NoteThreadState配置了線程對象狀態(tài)密鑰溯香。
  • 這里要注意的是當(dāng)前活動的線程不一定獲得了GIL
  • 由于主線程子線程都對應(yīng)操作系統(tǒng)的原生線程鲫构,而操作系統(tǒng)級別的線程調(diào)度和python級別的線程調(diào)度不同,所以操作系統(tǒng)系統(tǒng)是可能在主線程子線程之間切換的逐哈。
  • 但是當(dāng)所有的線程都完成了初始化動作之后芬迄,操作系統(tǒng)的線程調(diào)度和python的線程調(diào)度才會統(tǒng)一问顷。
  • 那時python的線程調(diào)度會迫使當(dāng)前活動線程釋放GIL昂秃,而這一操作會觸發(fā)操作系統(tǒng)內(nèi)核的用于管理線程調(diào)度的對象,進(jìn)而觸發(fā)操作系統(tǒng)對線程的調(diào)度杜窄。
  • 回到上一章肠骆,子線程開始了與主線程對GIL的競爭:
Modules\_threadmodule.c

static void
t_bootstrap(void *boot_raw)
{
    struct bootstate *boot = (struct bootstate *) boot_raw;
    PyThreadState *tstate;
    PyObject *res;

    tstate = boot->tstate;
    tstate->thread_id = PyThread_get_thread_ident();
    _PyThreadState_Init(tstate);
    PyEval_AcquireThread(tstate);
    tstate->interp->num_threads++;
    res = PyObject_Call(boot->func, boot->args, boot->keyw);
    ... ...
}
  • 主線程和子線程通過PyEval_AcquireThread爭奪GIL:
Python\ceval.c

void
PyEval_AcquireThread(PyThreadState *tstate)
{
    if (tstate == NULL)
        Py_FatalError("PyEval_AcquireThread: NULL new thread state");
    /* Check someone has called PyEval_InitThreads() to create the lock */
    assert(gil_created());
    take_gil(tstate);
    if (PyThreadState_Swap(tstate) != NULL)
        Py_FatalError(
            "PyEval_AcquireThread: non-NULL old thread state");
}
  • 這里有一個關(guān)鍵方法PyThreadState_Swap之前沒有提到:
Python\pystate.c

PyThreadState *
PyThreadState_Swap(PyThreadState *newts)
{
    PyThreadState *oldts = GET_TSTATE();

    SET_TSTATE(newts);
    /* It should not be possible for more than one thread state
       to be used for a thread.  Check this the best we can in debug
       builds.
    */
#if defined(Py_DEBUG)
    if (newts) {
        /* This can be called from PyEval_RestoreThread(). Similar
           to it, we need to ensure errno doesn't change.
        */
        int err = errno;
        PyThreadState *check = PyGILState_GetThisThreadState();
        if (check && check->interp == newts->interp && check != newts)
            Py_FatalError("Invalid thread state for this thread");
        errno = err;
    }
#endif
    return oldts;
}
  • 當(dāng)子線程被Python的線程調(diào)度機(jī)制喚醒后,首先就要通過PyThreadState_Swap將Python維護(hù)的當(dāng)前線程狀態(tài)對象設(shè)置為其自身的狀態(tài)對象塞耕。
  • 之后子線程繼續(xù)完成初始化蚀腿,并最終進(jìn)入解釋器,被Python線程調(diào)度機(jī)制控制。
  • 這里需要再次強(qiáng)調(diào)一下莉钙,thread_PyThread_start_new_thread是從主線程中執(zhí)行廓脆,而從t_bootstrap開始,則是在子線程中執(zhí)行的磁玉。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末停忿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蚊伞,更是在濱河造成了極大的恐慌席赂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件时迫,死亡現(xiàn)場離奇詭異颅停,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掠拳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門癞揉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溺欧,你說我怎么就攤上這事烧董。” “怎么了胧奔?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵逊移,是天一觀的道長。 經(jīng)常有香客問我龙填,道長胳泉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任岩遗,我火速辦了婚禮扇商,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宿礁。我一直安慰自己案铺,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布梆靖。 她就那樣靜靜地躺著控汉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪返吻。 梳的紋絲不亂的頭發(fā)上姑子,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機(jī)與錄音测僵,去河邊找鬼街佑。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沐旨。 我是一名探鬼主播森逮,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磁携!你這毒婦竟也來了吊宋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颜武,失蹤者是張志新(化名)和其女友劉穎璃搜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳞上,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡这吻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了篙议。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唾糯。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鬼贱,靈堂內(nèi)的尸體忽然破棺而出移怯,到底是詐尸還是另有隱情,我是刑警寧澤这难,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布舟误,位于F島的核電站,受9級特大地震影響姻乓,放射性物質(zhì)發(fā)生泄漏嵌溢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一蹋岩、第九天 我趴在偏房一處隱蔽的房頂上張望赖草。 院中可真熱鬧,春花似錦剪个、人聲如沸秧骑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乎折。三九已至,卻和暖如春如暖,著一層夾襖步出監(jiān)牢的瞬間笆檀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工盒至, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓枷遂,卻偏偏與公主長得像樱衷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酒唉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容