balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
當(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è)線程澜沟。
如果寫一個(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寫個(gè)死循環(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()
可以看到!!啟動(dòng)與CPU核心數(shù)量相同的N個(gè)線程,在4核CPU上可以監(jiān)控到CPU占用率僅有160%名船,也就是使用不到兩核
即使啟動(dòng)100個(gè)線程,使用率也就170%左右渠驼,仍然不到兩核百揭。
但是用C器一、C++或Java來(lái)改寫相同的死循環(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丹泉,要真正利用多核筋岛,除非重寫一個(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鎖,互不影響邪驮。(這里要比較一下得失了,是否非要使用線程,若是放棄線程使用進(jìn)程有什么得失,廖老師在這里并沒(méi)有提到)
小結(jié)
多線程編程莫辨,模型復(fù)雜,容易發(fā)生沖突毅访,必須用鎖加以隔離沮榜,同時(shí),又要小心死鎖的發(fā)生喻粹。
Python解釋器由于設(shè)計(jì)時(shí)有GIL全局鎖蟆融,導(dǎo)致了多線程無(wú)法利用多核。多線程的并發(fā)在Python中就是一個(gè)美麗的夢(mèng)守呜。