Python多線程(三):鎖

上一篇:多線程編程

Python多線程(一):GIL中我們提到了競態(tài)條件問題棠绘,即不同線程修改相同的共享變量出現(xiàn)運(yùn)行多次結(jié)果不一樣的問題嚎杨,即使CPython中有GIL座云,這種問題依然存在。現(xiàn)在我們通過多線程的鎖機(jī)制來解決這個問題旁理。

還是相同的代碼:

import threading

total = 0
def add():
    global total
    for i in range(1000000):
        total += 1

def desc():
    global total
    for i in range(1000000):
        total -= 1

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(total)

之前我們的分析的原因在于:兩加法減法操作在底層實(shí)現(xiàn)的時候有多個步驟樊零,由于GIL的切換導(dǎo)致字節(jié)碼交替運(yùn)行。如果我們能夠保證實(shí)現(xiàn)加法或者減法操作的時候只有一個線程在運(yùn)行孽文,就能解決這個問題驻襟。而保證某一代碼段只有一個線程在運(yùn)行的方法就是為這個線程加鎖夺艰,如下所示:

import threading

total = 0
lock = threading.Lock()
def add():
    global total
    global lock
    for i in range(1000000):
        lock.acquire()
        total += 1
        lock.release()

def desc():
    global total
    global lock
    for i in range(1000000):
        lock.acquire()
        total -= 1
        lock.release()

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(total)

運(yùn)行結(jié)果為0。在上面的代碼中沉衣,threading.Lock()實(shí)例化了一個鎖對象郁副,鎖對象有兩個方法:acquirerelease,分別是獲得鎖和釋放鎖厢蒜。當(dāng)一個線程獲得所時霞势,另外一個線程在acquire處阻塞,直到當(dāng)前鎖執(zhí)行release被釋放后才可以和其他線程共同爭奪鎖斑鸦。acquirerelease之間的代碼段執(zhí)行時不會切換到其他線程愕贡,保證了操作的完整性。

用鎖也存在問題巷屿,首先就是性能問題固以,在上面的例子中,不使用鎖運(yùn)行的執(zhí)行時間是0.15秒嘱巾,而使用鎖執(zhí)行時間是2.35秒憨琳,足足慢了15倍。

另外一個問題被稱為死鎖旬昭。當(dāng)一個線程調(diào)用子程序時篙螟,如果這個子程序也需要加鎖,則會出現(xiàn)這個問題:

import threading
import time

lock = threading.Lock()

def do_something():
    global lock
    lock.acquire()
    do_sub_task()
    lock.release()

def do_sub_task():
    global lock
    lock.acquire()
    time.sleep(2)
    lock.release()

thread = threading.Thread(target=do_something)
thread.start()
thread.join()

程序會在do_sub_task的首句阻塞问拘,因為該函數(shù)試圖去獲取鎖遍略,但是鎖并沒有釋放。解決方法有兩種:

  • 一種是通過threading.Lock()再實(shí)例化一把鎖骤坐,使得do_somethingdo_sub_task所需要的鎖不是同一把绪杏,這樣即使do_something獲取了鎖,do_sub_task也能夠獲得另外的鎖纽绍。但是這種方式的問題是當(dāng)這種情況出現(xiàn)很多蕾久,鎖就很難管理。
  • 另外一種是使用threading.RLock拌夏,這種鎖可以重復(fù)獲得僧著,只要釋放的次數(shù)等于獲得的次數(shù)即可。將上面代碼中的Lock換成RLock即可障簿。

還有一種死鎖情況稱為互相等待盹愚,參看下面代碼:

import threading
import time

lock1 = threading.Lock()
lock2 = threading.Lock()

def do_something1():
    lock1.acquire()
    time.sleep(2)
    lock2.acquire()
    print('Something 1 started')
    time.sleep(2)
    lock1.release()
    lock2.release()
    print('Something 1 ended')

def do_something2():
    lock2.acquire()
    time.sleep(3)
    lock1.acquire()
    print('Something 2 started')
    time.sleep(3)
    lock2.release()
    lock1.release()
    print('Something 2 ended')

thread1 = threading.Thread(target=do_something1)
thread2 = threading.Thread(target=do_something2)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

這個程序會一致阻塞,原因在于兩個線程獲得兩個鎖的順序是相反的卷谈,當(dāng)do_something1運(yùn)行時獲得lock1,然后執(zhí)行time.sleep(2)使得GIL釋放去執(zhí)行do_something2霞篡。do_something2獲得lock2后世蔗,同樣執(zhí)行time.sleep(3)使得GIL釋放去執(zhí)行do_something1端逼,do_something1此時需要獲得lock2才能繼續(xù)執(zhí)行,然而lock2do_something2處污淋,未釋放無法獲得顶滩。同理do_something2需要獲得的lock1do_something1處,也無法獲得寸爆。所以就出現(xiàn)了兩個線程互相等待的情況礁鲁。如果將其中某個線程獲得的鎖的順序交換,程序就能正常執(zhí)行赁豆。

可以看出仅醇,使用鎖機(jī)制很容易造成死鎖,在使用鎖的時候一定要小心魔种。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末析二,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子节预,更是在濱河造成了極大的恐慌叶摄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件安拟,死亡現(xiàn)場離奇詭異蛤吓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)糠赦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門会傲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愉棱,你說我怎么就攤上這事唆铐。” “怎么了奔滑?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵艾岂,是天一觀的道長。 經(jīng)常有香客問我朋其,道長王浴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任梅猿,我火速辦了婚禮氓辣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袱蚓。我一直安慰自己钞啸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著体斩,像睡著了一般梭稚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上絮吵,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天弧烤,我揣著相機(jī)與錄音,去河邊找鬼蹬敲。 笑死暇昂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伴嗡。 我是一名探鬼主播急波,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼闹究!你這毒婦竟也來了幔崖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤渣淤,失蹤者是張志新(化名)和其女友劉穎赏寇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體价认,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗅定,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了用踩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渠退。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脐彩,靈堂內(nèi)的尸體忽然破棺而出碎乃,到底是詐尸還是另有隱情,我是刑警寧澤惠奸,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布梅誓,位于F島的核電站,受9級特大地震影響佛南,放射性物質(zhì)發(fā)生泄漏梗掰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一嗅回、第九天 我趴在偏房一處隱蔽的房頂上張望及穗。 院中可真熱鬧,春花似錦绵载、人聲如沸埂陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焚虱。三九已至丸氛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間著摔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工定续, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谍咆,地道東北人。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓私股,卻偏偏與公主長得像摹察,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子倡鲸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

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

  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進(jìn)行...
    月亮是我踢彎得閱讀 5,959評論 3 28
  • 引言&動機(jī) 考慮一下這個場景峭状,我們有10000條數(shù)據(jù)需要處理克滴,處理每條數(shù)據(jù)需要花費(fèi)1秒,但讀取數(shù)據(jù)只需要0.1秒优床,...
    妄心xyx閱讀 917評論 0 30
  • 線程 操作系統(tǒng)線程理論 線程概念的引入背景 進(jìn)程 之前我們已經(jīng)了解了操作系統(tǒng)中進(jìn)程的概念胆敞,程序并不能單獨(dú)運(yùn)行着帽,只有...
    go以恒閱讀 1,635評論 0 6
  • 當(dāng)兵以后,不管何時再聽著這首歌移层,都會無限憂郁在心頭仍翰,我是一個頑皮叛逆的思想者,因為比較孤獨(dú)观话,高中階段形成了特別思想...
    中星道易閱讀 1,047評論 0 0
  • 對警察的最初印象予借,來自我的爺爺。 記憶中的他匪燕,高大魁梧蕾羊,雖然慈眉善目、但是不怒自威帽驯,雖然頭發(fā)花白龟再、但是眼神矍鑠,因...
    bq進(jìn)化論閱讀 125評論 0 1