奇怪的 dead lock

在服務(wù)器上程序中遇到一個(gè) import 卡死的情況惑折,而且這個(gè) bug 只能在服務(wù)器上重現(xiàn)术裸,我的電腦上不會(huì)重現(xiàn)。去掉一些無用的代碼停团,可以抽象出如下的代碼旷坦。

bar.py

# coding=utf-8

from threading import Thread

class Bar(Thread):
    def run(self):
        u"知乎".encode("utf-8")

bar = Bar()
bar.start()
bar.join()

foo.py

import bar

然后直接執(zhí)行 python foo.py,這時(shí)程序卡死不動(dòng)了佑稠。

首先必須要知道的是程序卡在哪里了秒梅,所以使用 trace 模塊去看程序的執(zhí)行流程。
執(zhí)行 python -m trace -t foo.py舌胶,這是程序調(diào)用的最后的部分捆蜀。

__init__.py(93):     for modname in modnames:
__init__.py(94):         if not modname or '.' in modname:
__init__.py(96):         try:
__init__.py(99):             mod = __import__('encodings.' + modname, fromlist=_import_tail,
__init__.py(100):                              level=0)
threading.py(237):         waiter = _allocate_lock()
threading.py(238):         waiter.acquire()
threading.py(239):         self.__waiters.append(waiter)
threading.py(240):         saved_state = self._release_save()
 --- modulename: threading, funcname: _release_save
threading.py(220):         self.__lock.release()           # No state to save
threading.py(241):         try:    # restore state no matter what (e.g., KeyboardInterrupt)
threading.py(242):             if timeout is None:
threading.py(243):                 waiter.acquire()

我們很明顯的看到了程序是卡在了獲得鎖的時(shí)候,但是我的程序里沒有明確的加鎖啊,為什么出現(xiàn)這種情況呢辆它?通過調(diào)用記錄向上追溯看到
mod = __import__('encodings.' + modname, fromlist=_import_tail, level=0)
是這一步引入了最后的鎖誊薄,發(fā)現(xiàn)包含這行代碼的文件是 /usr/lib/python2.7/encodings/__init__.py,大致猜出是執(zhí)行u"知乎".encode("utf-8")卡死的娩井。

現(xiàn)在再看 __import__ 的實(shí)現(xiàn),發(fā)現(xiàn) PyImport_ImportModuleLevel 調(diào)用了 _PyImport_AcquireLock似袁,當(dāng) import_module_level 成功后調(diào)用 _PyImport_ReleaseLock洞辣。

PyObject *
PyImport_ImportModuleLevel(char *name, PyObject *globals, PyObject *locals,
                         PyObject *fromlist, int level)
{
    PyObject *result;
    _PyImport_AcquireLock();
    result = import_module_level(name, globals, locals, fromlist, level);
    if (_PyImport_ReleaseLock() < 0) {
        Py_XDECREF(result);
        PyErr_SetString(PyExc_RuntimeError,
                        "not holding the import lock");
        return NULL;
    }
    return result;
}

再去繼續(xù)看 _PyImport_AcquireLock 的代碼可以明顯的看到有一個(gè) import_lock 存在。也就是 import 的時(shí)候會(huì)引入import_lock, 當(dāng)我們 import bar 的時(shí)候昙衅,首先會(huì)獲得 import_lock扬霜,但是當(dāng)我們執(zhí)行到 mod = __import__('encodings.' + modname, fromlist=_import_tail, level=0)的時(shí)候新創(chuàng)建的線程會(huì)再次請(qǐng)求獲得這把import_lock。在一把鎖內(nèi)部而涉,再次請(qǐng)求獲得這把鎖造成了死鎖著瓶,使程序直接卡住了。在服務(wù)器上把u"知乎".encode("utf-8") 換成 import socket 照樣會(huì)卡在 import_lock 處啼县。

通過分析材原,現(xiàn)在終于找出原因了。但是為什么只能在服務(wù)上重現(xiàn)呢季眷?為什么本地的機(jī)器沒有問題余蟹?

我把 u"知乎".encode("utf-8") 換成 import socket ,在本地執(zhí)行也會(huì)卡在 import_lock子刮。那為什么 u"知乎".encode("utf-8") 為啥在本地不卡呢威酒。那就用 ipdb 看看u"知乎".encode("utf-8")在本地和服務(wù)器上的調(diào)用有啥不同吧。

 #coding=utf-8
  
 import ipdb
 ipdb.set_trace()
 u"知乎".encode("utf-8")

結(jié)果發(fā)現(xiàn)在本地根本就沒有進(jìn)入 search_function挺峡,程序執(zhí)行完葵孤。而在服務(wù)器上直接進(jìn)入了 /usr/lib/python2.7/encodings/__init__.py 文件作郭,逐步的執(zhí)行到 __import__ 造成了死鎖市埋。為什么本地的機(jī)器上不用加載呢采呐?

在本地的 encodings/init.py 文件里加上調(diào)試信息 print encoding饮睬,發(fā)現(xiàn)在本地直接輸入 python 啟動(dòng)命令行胎食,直接就打印出了 utf-8危队,而在服務(wù)器上是 ascii习贫。原來不同的機(jī)器上加載的默認(rèn)編碼不一樣歉井。通過 locale.getdefaultlocale 也發(fā)現(xiàn)默認(rèn)的編碼服務(wù)器默認(rèn)的編碼是 ascii送挑,本地是 utf-8绑莺。

終于知道了原來根據(jù)環(huán)境不同,默認(rèn)加載的編碼是不一樣的惕耕,加載了的編碼會(huì)有 cache 就不用執(zhí)行到 __import__纺裁,沒有加載過的編碼就會(huì)執(zhí)行。我又在自己的服務(wù)器上把 encode("utf-8") 改成encode("utf8"),發(fā)現(xiàn)本地的程序也卡在了 __import__ 的地方欺缘。

至此這個(gè) bug栋豫,終于搞清楚了,真是艱難谚殊。簡(jiǎn)單總結(jié)下

一般在最外層只寫函數(shù)丧鸯,類,變量定義代碼嫩絮。其它有副作用的代碼都放到函數(shù)里丛肢,尤其不能在最外層寫 Thread.join 這種會(huì) block 住整個(gè)程序運(yùn)行的代碼。import 完之后再顯式調(diào)用函數(shù)來執(zhí)行這些代碼剿干。 原則是使 import 盡量不帶有副作用蜂怎。

這個(gè)問題得以解決,絕大部分的功勞屬于安江澤置尔。同時(shí)感謝Leo Jay 的指正杠步。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榜轿,隨后出現(xiàn)的幾起案子幽歼,更是在濱河造成了極大的恐慌,老刑警劉巖谬盐,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件试躏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡设褐,警方通過查閱死者的電腦和手機(jī)颠蕴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來助析,“玉大人犀被,你說我怎么就攤上這事⊥饧剑” “怎么了寡键?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)雪隧。 經(jīng)常有香客問我西轩,道長(zhǎng),這世上最難降的妖魔是什么脑沿? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任藕畔,我火速辦了婚禮,結(jié)果婚禮上庄拇,老公的妹妹穿的比我還像新娘注服。我一直安慰自己韭邓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布溶弟。 她就那樣靜靜地躺著女淑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辜御。 梳的紋絲不亂的頭發(fā)上鸭你,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音擒权,去河邊找鬼袱巨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菜拓,可吹牛的內(nèi)容都是我干的瓣窄。 我是一名探鬼主播笛厦,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼纳鼎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了裳凸?” 一聲冷哼從身側(cè)響起贱鄙,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姨谷,沒想到半個(gè)月后逗宁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梦湘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年瞎颗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌议。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哼拔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓣颅,到底是詐尸還是另有隱情倦逐,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布宫补,位于F島的核電站檬姥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏粉怕。R本人自食惡果不足惜健民,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贫贝。 院中可真熱鬧荞雏,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至筑辨,卻和暖如春俺驶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棍辕。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工暮现, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人楚昭。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓栖袋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親抚太。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塘幅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法尿贫,內(nèi)部類的語法电媳,繼承相關(guān)的語法,異常的語法庆亡,線程的語...
    子非魚_t_閱讀 31,644評(píng)論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理匾乓,服務(wù)發(fā)現(xiàn),斷路器又谋,智...
    卡卡羅2017閱讀 134,672評(píng)論 18 139
  • 字符集和編碼簡(jiǎn)介 在編程中常称捶欤可以見到各種字符集和編碼,包括ASCII,MBCS,Unicode等字符集彰亥。確切的說...
    蘭山小亭閱讀 8,498評(píng)論 0 13
  • http://python.jobbole.com/85231/ 關(guān)于專業(yè)技能寫完項(xiàng)目接著寫寫一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,579評(píng)論 1 118
  • 十月已經(jīng)快過完了咧七,我在這一個(gè)月做了什么有什么收獲。 氣溫的驟降讓我的的熱情也下降了許多剩愧,不管是對(duì)生活猪叙,學(xué)習(xí)還是減肥...
    A_insist閱讀 421評(píng)論 0 0