最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

13.2 多線程

2021-09-29 08:19 作者:海鷗之道  | 我要投稿

多任務(wù)可以由多進(jìn)程完成,也可以由一個(gè)進(jìn)程內(nèi)的多線程完成。

我們前面提到了進(jìn)程是由若干線程組成的,一個(gè)進(jìn)程至少有一個(gè)線程。

由于線程是操作系統(tǒng)直接支持的執(zhí)行單元,因此,高級(jí)語(yǔ)言通常都內(nèi)置多線程的支持,Python也不例外,并且,Python的線程是真正的Posix Thread,而不是模擬出來(lái)的線程。

Python的標(biāo)準(zhǔn)庫(kù)提供了兩個(gè)模塊:_threadthreading,_thread是低級(jí)模塊,threading是高級(jí)模塊,對(duì)_thread進(jìn)行了封裝。絕大多數(shù)情況下,我們只需要使用threading這個(gè)高級(jí)模塊。

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

執(zhí)行結(jié)果如下:

由于任何進(jìn)程默認(rèn)就會(huì)啟動(dòng)一個(gè)線程,我們把該線程稱為主線程,主線程又可以啟動(dòng)新的線程,Python的threading模塊有個(gè)current_thread()函數(shù),它永遠(yuǎn)返回當(dāng)前線程的實(shí)例。主線程實(shí)例的名字叫MainThread,子線程的名字在創(chuàng)建時(shí)指定,我們用LoopThread命名子線程。名字僅僅在打印時(shí)用來(lái)顯示,完全沒(méi)有其他意義,如果不起名字Python就自動(dòng)給線程命名為Thread-1,Thread-2……

Lock

多線程和多進(jìn)程最大的不同在于,多進(jìn)程中,同一個(gè)變量,各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個(gè)變量都可以被任何一個(gè)線程修改,因此,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)改一個(gè)變量,把內(nèi)容給改亂了。

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

輸出:

我們定義了一個(gè)共享變量balance,初始值為0,并且啟動(dòng)兩個(gè)線程,先存后取,理論上結(jié)果應(yīng)該為0,但是,由于線程的調(diào)度是由操作系統(tǒng)決定的,當(dāng)t1、t2交替執(zhí)行時(shí),只要循環(huán)次數(shù)足夠多,balance的結(jié)果就不一定是0了。

原因是因?yàn)楦呒?jí)語(yǔ)言的一條語(yǔ)句在CPU執(zhí)行時(shí)是若干條語(yǔ)句,即使一個(gè)簡(jiǎn)單的計(jì)算:

也分兩步:

  1. 計(jì)算balance + n,存入臨時(shí)變量中;

  2. 將臨時(shí)變量的值賦給balance。

也就是可以看成:

由于x是局部變量,兩個(gè)線程各自都有自己的x,當(dāng)代碼正常執(zhí)行時(shí):

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

究其原因,是因?yàn)樾薷?code>balance需要多條語(yǔ)句,而執(zhí)行這幾條語(yǔ)句時(shí),線程可能中斷,從而導(dǎo)致多個(gè)線程把同一個(gè)對(duì)象的內(nèi)容改亂了。

兩個(gè)線程同時(shí)一存一取,就可能導(dǎo)致余額不對(duì),你肯定不希望你的銀行存款莫名其妙地變成了負(fù)數(shù),所以,我們必須確保一個(gè)線程在修改balance的時(shí)候,別的線程一定不能改。

如果我們要確保balance計(jì)算正確,就要給change_it()上一把鎖,當(dāng)某個(gè)線程開(kāi)始執(zhí)行change_it()時(shí),我們說(shuō),該線程因?yàn)楂@得了鎖,因此其他線程不能同時(shí)執(zhí)行change_it(),只能等待,直到鎖被釋放后,獲得該鎖以后才能改。由于鎖只有一個(gè),無(wú)論多少線程,同一時(shí)刻最多只有一個(gè)線程持有該鎖,所以,不會(huì)造成修改的沖突。創(chuàng)建一個(gè)鎖就是通過(guò)threading.Lock()來(lái)實(shí)現(xiàn):

當(dāng)多個(gè)線程同時(shí)執(zhí)行lock.acquire()時(shí),只有一個(gè)線程能成功地獲取鎖,然后繼續(xù)執(zhí)行代碼,其他線程就繼續(xù)等待直到獲得鎖為止。

獲得鎖的線程用完后一定要釋放鎖,否則那些苦苦等待鎖的線程將永遠(yuǎn)等待下去,成為死線程。所以我們用try...finally來(lái)確保鎖一定會(huì)被釋放。

鎖的好處就是確保了某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾完整地執(zhí)行,壞處當(dāng)然也很多,首先是阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼實(shí)際上只能以單線程模式執(zhí)行,效率就大大地下降了。其次,由于可以存在多個(gè)鎖,不同的線程持有不同的鎖,并試圖獲取對(duì)方持有的鎖時(shí),可能會(huì)造成死鎖,導(dǎo)致多個(gè)線程全部掛起,既不能執(zhí)行,也無(wú)法結(jié)束,只能靠操作系統(tǒng)強(qiáng)制終止。

多核CPU

如果你不幸擁有一個(gè)多核CPU,你肯定在想,多核應(yīng)該可以同時(shí)執(zhí)行多個(gè)線程。

如果寫(xiě)一個(gè)死循環(huán)的話,會(huì)出現(xiàn)什么情況呢?

打開(kāi)Mac OS X的Activity Monitor,或者Windows的Task Manager,都可以監(jiān)控某個(gè)進(jìn)程的CPU使用率。

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

如果有兩個(gè)死循環(huán)線程,在多核CPU中,可以監(jiān)控到會(huì)占用200%的CPU,也就是占用兩個(gè)CPU核心。

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

試試用Python寫(xiě)個(gè)死循環(huán):

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

但是用C、C++或Java來(lái)改寫(xiě)相同的死循環(huán),直接可以把全部核心跑滿,4核就跑到400%,8核就跑到800%,為什么Python不行呢?

因?yàn)镻ython的線程雖然是真正的線程,但解釋器執(zhí)行代碼時(shí),有一個(gè)GIL鎖:Global Interpreter Lock,任何Python線程執(zhí)行前,必須先獲得GIL鎖,然后,每執(zhí)行100條字節(jié)碼,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行。這個(gè)GIL全局鎖實(shí)際上把所有線程的執(zhí)行代碼都給上了鎖,所以,多線程在Python中只能交替執(zhí)行,即使100個(gè)線程跑在100核CPU上,也只能用到1個(gè)核。

GIL是Python解釋器設(shè)計(jì)的歷史遺留問(wèn)題,通常我們用的解釋器是官方實(shí)現(xiàn)的CPython,要真正利用多核,除非重寫(xiě)一個(gè)不帶GIL的解釋器。

所以,在Python中,可以使用多線程,但不要指望能有效利用多核。如果一定要通過(guò)多線程利用多核,那只能通過(guò)C擴(kuò)展來(lái)實(shí)現(xiàn),不過(guò)這樣就失去了Python簡(jiǎn)單易用的特點(diǎn)。

不過(guò),也不用過(guò)于擔(dān)心,Python雖然不能利用多線程實(shí)現(xiàn)多核任務(wù),但可以通過(guò)多進(jìn)程實(shí)現(xiàn)多核任務(wù)。多個(gè)Python進(jìn)程有各自獨(dú)立的GIL鎖,互不影響。

小結(jié)

多線程編程,模型復(fù)雜,容易發(fā)生沖突,必須用鎖加以隔離,同時(shí),又要小心死鎖的發(fā)生。

Python解釋器由于設(shè)計(jì)時(shí)有GIL全局鎖,導(dǎo)致了多線程無(wú)法利用多核。多線程的并發(fā)在Python中就是一個(gè)美麗的夢(mèng)。


13.2 多線程的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
龙川县| 德昌县| 青铜峡市| 邹城市| 怀安县| 永昌县| 尖扎县| 太白县| 垣曲县| 察雅县| 阳原县| 辛集市| 定安县| 新竹市| 台中市| 江油市| 白银市| 芷江| 东乡族自治县| 长宁区| 郴州市| 东辽县| 庄浪县| 砀山县| 淮滨县| 德令哈市| 上虞市| 麻江县| 丹寨县| 丰城市| 苍溪县| 临沧市| 五原县| 玉屏| 通州市| 砚山县| 东山县| 会昌县| 四平市| 汉中市| 淮阳县|