Python學習6進程與線程2

多線程

多任務可以由多進程完成斧抱,也可以由一個進程內(nèi)的多線程完成。進程是由若干線程組成的渐溶,一個進程至少有一個線程。Python的標準庫提供了兩個模塊:_thread和threading茎辐,_thread是低級模塊宪郊,threading是高級模塊拖陆,對_thread進行了封裝弛槐。絕大多數(shù)情況下,我們只需要使用threading這個高級模塊依啰。

啟動一個線程就是把一個函數(shù)傳入并創(chuàng)建Thread實例乎串,然后調(diào)用start()開始執(zhí)行:

import time, threading

# 新線程執(zhí)行的代碼:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

執(zhí)行結果如下:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

由于任何進程默認就會啟動一個線程,我們把該線程稱為主線程速警,主線程又可以啟動新的線程艰争,Python的threading模塊有個current_thread()函數(shù),它永遠返回當前線程的實例桂对。主線程實例的名字叫MainThread鸠匀,子線程的名字在創(chuàng)建時指定蕉斜,我們用LoopThread命名子線程。名字僅僅在打印時用來顯示缀棍,完全沒有其他意義宅此,如果不起名字Python就自動給線程命名為Thread-1,Thread-2……

  • Lock(重要)

多線程和多進程最大的不同在于爬范,多進程中父腕,同一個變量,各自有一份拷貝存在于每個進程中青瀑,互不影響,而多線程中斥难,所有變量都由所有線程共享,所以哑诊,任何一個變量都可以被任何一個線程修改群扶,因此镀裤,線程之間共享數(shù)據(jù)最大的危險在于多個線程同時改一個變量竞阐,把內(nèi)容給改亂了暑劝。

來看看多個線程同時操作一個變量怎么把內(nèi)容給改亂了:

import time, threading

# 假定這是你的銀行存款:
balance = 0

def change_it(n):
    # 先存后取骆莹,結果應該為0:
    global balance
    balance = balance + n
    balance = balance - n

def run_thread(n):
    for i in range(100000):
        change_it(n)

t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

我們定義了一個共享變量balance铃岔,初始值為0汪疮,并且啟動兩個線程,先存后取毁习,理論上結果應該為0,但是纺且,由于線程的調(diào)度是由操作系統(tǒng)決定的,當t1载碌、t2交替執(zhí)行時猜嘱,只要循環(huán)次數(shù)足夠多,balance的結果就不一定是0了朗伶。

原因是因為高級語言的一條語句在CPU執(zhí)行時是若干條語句,即使一個簡單的計算:

balance = balance + n
也分兩步:

計算balance + n论皆,存入臨時變量中益楼;
將臨時變量的值賦給balance点晴。
也就是可以看成:

x = balance + n
balance = x

由于x是局部變量感凤,兩個線程各自都有自己的x粒督,當代碼正常執(zhí)行時:

初始值 balance = 0

t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1     # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1     # balance = 0

t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2     # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2     # balance = 0

結果 balance = 0

但是t1和t2是交替運行的陪竿,如果操作系統(tǒng)以下面的順序執(zhí)行t1屠橄、t2:

初始值 balance = 0

t1: x1 = balance + 5  # x1 = 0 + 5 = 5

t2: x2 = balance + 8  # x2 = 0 + 8 = 8
t2: balance = x2      # balance = 8

t1: balance = x1      # balance = 5
t1: x1 = balance - 5  # x1 = 5 - 5 = 0
t1: balance = x1      # balance = 0

t2: x2 = balance - 8  # x2 = 0 - 8 = -8
t2: balance = x2   # balance = -8

結果 balance = -8

究其原因族跛,是因為修改balance需要多條語句仇矾,而執(zhí)行這幾條語句時庸蔼,線程可能中斷贮匕,從而導致多個線程把同一個對象的內(nèi)容改亂了姐仅。

兩個線程同時一存一取刻盐,就可能導致余額不對掏膏,你肯定不希望你的銀行存款莫名其妙地變成了負數(shù),所以敦锌,我們必須確保一個線程在修改balance的時候馒疹,別的線程一定不能改。

如果我們要確保balance計算正確乙墙,就要給change_it()上一把鎖颖变,當某個線程開始執(zhí)行change_it()時,我們說听想,該線程因為獲得了鎖腥刹,因此其他線程不能同時執(zhí)行change_it()汉买,只能等待衔峰,直到鎖被釋放后,獲得該鎖以后才能改垫卤。由于鎖只有一個威彰,無論多少線程穴肘,同一時刻最多只有一個線程持有該鎖歇盼,所以梢褐,不會造成修改的沖突旺遮。創(chuàng)建一個鎖就是通過threading.Lock()來實現(xiàn):

balance = 0
lock = threading.Lock()

def run_thread(n):
    for i in range(100000):
        # 先要獲取鎖:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要釋放鎖:
            lock.release()

當多個線程同時執(zhí)行l(wèi)ock.acquire()時盈咳,只有一個線程能成功地獲取鎖边翼,然后繼續(xù)執(zhí)行代碼鱼响,其他線程就繼續(xù)等待直到獲得鎖為止组底。

獲得鎖的線程用完后一定要釋放鎖丈积,否則那些苦苦等待鎖的線程將永遠等待下去,成為死線程江滨。所以我們用try...finally來確保鎖一定會被釋放。

鎖的好處就是確保了某段關鍵代碼只能由一個線程從頭到尾完整地執(zhí)行厌均,壞處當然也很多,首先是阻止了多線程并發(fā)執(zhí)行棺弊,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了模她。其次,由于可以存在多個鎖侈净,不同的線程持有不同的鎖尊勿,并試圖獲取對方持有的鎖時畜侦,可能會造成死鎖元扔,導致多個線程全部掛起夏伊,既不能執(zhí)行摇展,也無法結束,只能靠操作系統(tǒng)強制終止咏连。

  • 多核CPU

如果你不幸擁有一個多核CPU,你肯定在想祟滴,多核應該可以同時執(zhí)行多個線程振惰。

如果寫一個死循環(huán)的話垄懂,會出現(xiàn)什么情況呢骑晶?

打開Mac OS X的Activity Monitor草慧,或者Windows的Task Manager桶蛔,都可以監(jiān)控某個進程的CPU使用率漫谷。

我們可以監(jiān)控到一個死循環(huán)線程會100%占用一個CPU仔雷。

如果有兩個死循環(huán)線程舔示,在多核CPU中碟婆,可以監(jiān)控到會占用200%的CPU惕稻,也就是占用兩個CPU核心若皱。

要想把N核CPU的核心全部跑滿享潜,就必須啟動N個死循環(huán)線程。

試試用Python寫個死循環(huán):

import threading, multiprocessing

def loop():
    x = 0
    while True:
        x = x ^ 1

for i in range(multiprocessing.cpu_count()):
    t = threading.Thread(target=loop)
    t.start()

啟動與CPU核心數(shù)量相同的N個線程锻煌,在4核CPU上可以監(jiān)控到CPU占用率僅有102%妓布,也就是僅使用了一核宋梧。

但是用C匣沼、C++或Java來改寫相同的死循環(huán)捂龄,直接可以把全部核心跑滿释涛,4核就跑到400%倦沧,8核就跑到800%唇撬,為什么Python不行呢展融?

因為Python的線程雖然是真正的線程窖认,但解釋器執(zhí)行代碼時,有一個GIL鎖:Global Interpreter Lock扑浸,任何Python線程執(zhí)行前烧给,必須先獲得GIL鎖喝噪,然后础嫡,每執(zhí)行100條字節(jié)碼酝惧,解釋器就自動釋放GIL鎖榴鼎,讓別的線程有機會執(zhí)行晚唇。這個GIL全局鎖實際上把所有線程的執(zhí)行代碼都給上了鎖,所以哩陕,多線程在Python中只能交替執(zhí)行翁涤,即使100個線程跑在100核CPU上萌踱,也只能用到1個核。

GIL是Python解釋器設計的歷史遺留問題并鸵,通常我們用的解釋器是官方實現(xiàn)的CPython,要真正利用多核扔涧,除非重寫一個不帶GIL的解釋器。

所以枯夜,在Python中,可以使用多線程湖雹,但不要指望能有效利用多核咏闪。如果一定要通過多線程利用多核摔吏,那只能通過C擴展來實現(xiàn)鸽嫂,不過這樣就失去了Python簡單易用的特點征讲。

不過据某,也不用過于擔心诗箍,Python雖然不能利用多線程實現(xiàn)多核任務癣籽,但可以通過多進程實現(xiàn)多核任務。多個Python進程有各自獨立的GIL鎖筷狼,互不影響。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桑逝,一起剝皮案震驚了整個濱河市棘劣,隨后出現(xiàn)的幾起案子楞遏,更是在濱河造成了極大的恐慌茬暇,老刑警劉巖寡喝,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異预鬓,居然都是意外死亡巧骚,警方通過查閱死者的電腦和手機格二,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顶猜,“玉大人沧奴,你說我怎么就攤上這事√戏停” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵疮绷,是天一觀的道長。 經(jīng)常有香客問我嚣潜,道長,這世上最難降的妖魔是什么郑原? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮犯犁,結果婚禮上,老公的妹妹穿的比我還像新娘酸役。我一直安慰自己住诸,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布贱呐。 她就那樣靜靜地躺著,像睡著了一般奄薇。 火紅的嫁衣襯著肌膚如雪驳阎。 梳的紋絲不亂的頭發(fā)上馁蒂,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天呵晚,我揣著相機與錄音沫屡,去河邊找鬼饵隙。 笑死沮脖,一個胖子當著我的面吹牛金矛,可吹牛的內(nèi)容都是我干的勺届。 我是一名探鬼主播驶俊,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼免姿,長吁一口氣:“原來是場噩夢啊……” “哼废睦!你這毒婦竟也來了养泡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤澜掩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后杖挣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡惩妇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了歌殃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡氓皱,死狀恐怖勃刨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情股淡,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布唯灵,位于F島的核電站,受9級特大地震影響埠帕,放射性物質(zhì)發(fā)生泄漏垢揩。R本人自食惡果不足惜搞监,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一水孩、第九天 我趴在偏房一處隱蔽的房頂上張望琐驴。 院中可真熱鬧俘种,春花似錦绝淡、人聲如沸宙刘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馍乙。三九已至布近,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撑瞧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工显蝌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人曼尊。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像骆撇,于是被迫代替她去往敵國和親瞒御。 傳聞我的和親對象是個殘疾皇子神郊,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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

  • 進程是cpu資源分配的最小單位高每,線程是cpu調(diào)度的最小單位。以前進程既是資源分配也是調(diào)度的最小單位践宴,后來為了更合理...
    nine_9閱讀 629評論 0 1
  • 什么是進程,什么是線程阻肩,進程和線程的關系這里就不說了,直接講將Python中如何創(chuàng)建多進程與多線程吧烤惊。 多進程 U...
    DramaScript閱讀 397評論 1 2
  • 引言 從剛開始學習Python爬蟲的時候乔煞,就一直惦記著多線程這個東西柒室,想想每次下載圖片都是單線程渡贾,一個下完繼續(xù)下一...
    coder_pig閱讀 895評論 0 7
  • 1 十年前擂仍,喬依念高三,小提琴已經(jīng)學了12年逢渔,學校的新年聯(lián)歡晚會上一曲《云雀》明快歡騰肋坚,她收起琴弓肃廓,微笑著朝臺下鞠...
    有故事的牛魔王閱讀 629評論 1 1
  • 肖皇妃:“此一番智厌,劉子安功不可沒盲赊,可總是賞你些黃白之物也頗沒有味道……” 我拱手:“草民能為皇上和肖皇妃效力已是極...
    銹鐘瑟鼓閱讀 355評論 0 1