什么是線程?
要弄清楚線程的定義,往往就要和進(jìn)程相互比較,從比較中才能更準(zhǔn)確地明白一個東西的定義。首先有個數(shù)量關(guān)系是這樣的,進(jìn)程是由多線程組成的,每個進(jìn)程起碼都有一個線程。
然后重點是有人說:
進(jìn)程和線程簡單而基本靠譜的定義如下:
1. 進(jìn)程:程序的一次執(zhí)行
2. 線程:CPU的基本調(diào)度單位
給一句話的定義,其實沒有什么意思。但是,做為最根本的落腳點,放著也是極好的。
阮大神的這篇blog寫的很通俗,但是不清楚,不深入,但是文章加評論卻是相當(dāng)有意思。
在我眼中的定義的理解
- 進(jìn)程:程序的一次執(zhí)行?
所謂程序的一次執(zhí)行,就是指有自己的內(nèi)存空間。就程序代碼而言,就是當(dāng)有全局變量,局部變量的時候,兩者是不會互相影響的,每個進(jìn)程都有自己獨立的內(nèi)存空間。其實,這些都是操作系統(tǒng)在影響著,你看,每個進(jìn)程都有自己的內(nèi)存空間,那么每個進(jìn)程自己使用自己的東西的時候,就不會拿錯東西,這樣一來,我的程序員師叔們編程序的時候就不會弄錯了,不用考慮這個變量是屬于哪個進(jìn)程這種問題了。(也不知道當(dāng)時他們是不是這么想的:( )(那么問題來了,操作系統(tǒng)層是這樣的,那么硬件層呢?CPU層到底是怎么實現(xiàn)的呢?) - 線程:CPU的基本調(diào)度單位?
這個問題還沒有考慮清楚,下次看看書,再來補(bǔ)充。有一個膚淺的一點就是在實際編程的過程中,全局變量是可以被所有線程所訪問到的。(太膚淺了。。)
Python的多線程實現(xiàn)(Python3實現(xiàn))
Python的標(biāo)準(zhǔn)庫提供了兩個模塊:_thread和threading,_thread是低級模塊,threading是高級模塊,對_thread進(jìn)行了封裝。絕大多數(shù)情況下,我們只需要使用threading這個高級模塊。
下面是廖大官網(wǎng)的例子,感覺非常不錯,搬過來了哈。
import time, threading
# 假定這是你的銀行存款:
balance = 0
def change_it(n):
# 先存后取,結(jié)果應(yīng)該為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)
如果是初次在學(xué)校中開始接觸到線程的話,其實有一點是十分困惑的。(反正我是非常困惑的)就是有一種欲望是想看看threading這個模塊的Thread函數(shù)的實現(xiàn)是什么?或者說就想弄清楚,弄明白run_thread,這個函數(shù)命傳遞進(jìn)去了之后,程序代碼到底做了什么?為什么要t1.start(),t1.join()?就很奇怪,一個好好的函數(shù),為什么要target傳一個函數(shù)名,args參數(shù)傳函數(shù)的參數(shù),一起傳不好嗎?
然后當(dāng)你真的點擊去看實現(xiàn)的時候,就會一臉懵逼。那么如果控制這種想法呢?反正我就明白一點就是,學(xué)習(xí)的過程,并不是你前面的都明白的,下一個點也能明白的,有時候明白這種明白,知道這個點不是現(xiàn)階段的問題這個事實是非常好的。
API中的幾個注意點:
- target傳遞的是一個函數(shù)名,而不是函數(shù)調(diào)用,如果你看過Python的線程的API的話,也是要注意這一點的。
- balance這種全局變量是可以被所有線程訪問到的。然后就有了鎖的概念,那不是另一個世界了。
- 線程的start(),join()函數(shù)的作用,線程的調(diào)度是由操作系統(tǒng)決定的,沒有先后順序,高級語言的有些一條語句其實可以拆分之類的概念,這里是不會提的,不然顯得沒重點,可以看一下廖大的教程,很詳細(xì)。
- 下面是其中一種運行結(jié)果。
初始值 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
結(jié)果 balance = -8
- 線程中鎖。
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要獲取鎖:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要釋放鎖:
lock.release()
還真是,這個API比JAVA中的要簡單很多。
- 還有一種是一個python的類繼承的thread的API還沒有寫,下次補(bǔ)充。顯示這個繼承的API不好,類中參雜了很多不是這個類的東西。
GIL
GIL是全局解釋器鎖,可以看這個東西的英文比較能懂。因為這個東西的存在,Python的多線程,不論怎么樣都只能充分使用CPU的一個核心。有下面幾點要注意:
- Python是一種語言,語言也可以理解為一種語法標(biāo)準(zhǔn),在這個標(biāo)準(zhǔn)里面是沒有GIL的,但是這個語言的實現(xiàn),官方的解釋去CPython中是用了GIL來實現(xiàn)的,但是比如JPython是沒有這個概念的。(那么問題來了,為什么還是有那么多人使用CPython?)
- Python3中的GIL也是一樣存在的,只是Python3中對GIL的一個多線程比單線程還要慢的這一個問題進(jìn)行了修復(fù),沒有從根本上解決這個問題。
- 想充分利用多核CPU的話,可以使用多進(jìn)程。(那么為什么多進(jìn)程可以避開GIL而充分使用CPU呢?)
最后之前
為什么多線程或者說多進(jìn)程會比單進(jìn)程快呢?對于IO密集型,計算密集型?可以從CPU和操作系統(tǒng)的角度來談?wù)勥@個問題嗎?計算機(jī)有幾個IO方式呢,比如DMA是什么,有什么不同?計算機(jī)IO的時候,是需要CPU的運行嗎?需要嗎?不需要嗎?不需要的話,又是誰把磁盤中的數(shù)據(jù)讀到內(nèi)存中的呢?除了CPU還有誰有這個能力呢?線程和進(jìn)程在CPU這個層面到底是什么瓜葛呢?待我好好看書,來回答這個問題。
最后
寫給未來的自己,也寫給讀到最后的你,能力有限,如有錯誤,請交流談?wù)摷爸刚?,如果有幫助,請評論或者點擊喜歡,謝謝。
參考:
進(jìn)程與線程的一個簡單解釋
廖雪峰的官方教程之多線程
Python 之父談 Python
Python 3.2與更好的GIL