一.線程的概念
概念:
在一個(gè)進(jìn)程的內(nèi)部养距,要同時(shí)干多件事棍厌,就需要同時(shí)運(yùn)行多個(gè)“子任務(wù)”,我們把進(jìn)程內(nèi)的這些“子任務(wù)”叫做線程敬肚。
線程通常叫做輕型的進(jìn)程艳馒。線程是共享內(nèi)存空間的并發(fā)執(zhí)行的多任務(wù),每一個(gè)線程都共享一個(gè)進(jìn)程的資源第美。
線程是最小的執(zhí)行單元什往,而進(jìn)程由至少一個(gè)線程組成,這些線程的運(yùn)行順序是不確定的慌闭、隨機(jī)的贡必、不可預(yù)測的仔拟。
模塊:
1飒赃、_thread模塊 低級(jí)模塊
2载佳、threading模塊 高級(jí)模塊,對_thread進(jìn)行了封裝
二.啟動(dòng)線程
在線程里面setDaemon()和join()方法都是常用的挠乳,他們的區(qū)別如下
join ()方法:主線程A中睡扬,創(chuàng)建了子線程B卖怜,并且在主線程A中調(diào)用了B.join()阐枣,那么,主線程A會(huì)在調(diào)用的地方等待甩鳄,直到子線程B完成操作后娩贷, 才可以接著往下執(zhí)行锁孟,那么在調(diào)用這個(gè)線程時(shí)可以使用被調(diào)用線程的join方法。join([timeout]) 里面的參數(shù)時(shí)可選的储笑,代表線程運(yùn)行的最大時(shí)間突倍,即如果超過這個(gè)時(shí)間,不管這個(gè)此線程有沒有執(zhí)行完畢都會(huì)被回收焊虏,然后主線程或函數(shù)都會(huì)接著執(zhí)行的诵闭,如果線程執(zhí)行時(shí)間小于參數(shù)表示的 時(shí)間澎嚣,則接著執(zhí)行易桃,不用一定要等待到參數(shù)表示的時(shí)間。
setDaemon()方法敌呈。主線程A中驱富,創(chuàng)建了子線程B匹舞,并且在主線程A中調(diào)用了B.setDaemon(),這個(gè)的意思是赐稽,把主線程A設(shè)置為守護(hù)線程,這時(shí)候晰绎,要是主線程A執(zhí)行結(jié)束了荞下,就不管子線程B是否完成,一并和主線程A退出.這就是setDaemon方法的含義,這基本和join是相反的仰税。此外陨簇,還有個(gè)要特別注意的:必須在start()方法調(diào)用之前設(shè)置迹淌,如果不設(shè)置為守護(hù)線程,程序會(huì)被無限掛起唉窃,只有等待了所有線程結(jié)束它才結(jié)束纹份。
import threading,time
def run(num):
print("子線程(%s)開始" % (threading.current_thread().name))
#實(shí)現(xiàn)線程的功能
time.sleep(2)
print("打印", num)
time.sleep(2)
print("子線程(%s)結(jié)束" % (threading.current_thread().name))
if __name__ == "__main__":
#任何進(jìn)程默認(rèn)就會(huì)啟動(dòng)一個(gè)線程矮嫉,稱為主線程牍疏,主線程可以啟動(dòng)新的子線程
#current_thread():返回返回當(dāng)前線程的實(shí)例
print("主線程(%s)啟動(dòng)" % (threading.current_thread().name))
#創(chuàng)建子線程 線程的名稱
t = threading.Thread(target=run, name="runThread", args=(1,))
t.start()
#t.setDaemon(True)#設(shè)置為后臺(tái)線程鳞陨,這里默認(rèn)是Fal#se,設(shè)置為True之后則主線程不用等待子線程
#等待線程結(jié)束
t.join()
print("主線程(%s)結(jié)束" % (threading.current_thread().name))
三.多線程共享數(shù)據(jù).
多線程和多進(jìn)程最大的不同在于援岩,多進(jìn)程中掏导,同一個(gè)變量趟咆,各自有一份拷貝存在每個(gè)進(jìn)程中,互不影響鳞贷。而多線程中虐唠,所有變量都由所有線程共享。所以搓幌,任何一個(gè)變量都可以被任意一個(gè)線程修改眷蚓,因此,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)修改一個(gè)變量叉钥,容易把內(nèi)容改亂了投队。
3.1造成數(shù)據(jù)混亂.
import threading
num = 0
def run(n):
global num
for i in range(10000000):
num = num + n
num = num - n
if __name__ == "__main__":
t1 = threading.Thread(target=run, args=(6,))
t2 = threading.Thread(target=run, args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num =",num)
3.2線程鎖解決數(shù)據(jù)混亂.
#鎖對象
lock = threading.Lock()
num = 0
def run(n):
global num
for i in range(10000000):
# 鎖
# 確保了這段代碼只能由一個(gè)線程從頭到尾的完整執(zhí)行
# 阻止了多線程的并發(fā)執(zhí)行敷鸦,包含鎖的某段代碼實(shí)際上只能以單線程模式執(zhí)行寝贡,所以效率大大滴降低了
# 由于可以存在多個(gè)鎖,不同線程持有不同的鎖碟案,并試圖獲取其他的鎖价说,可能造成死鎖风秤,導(dǎo)致多個(gè)線程掛起。只能靠操作系統(tǒng)強(qiáng)制終止
'''
lock.acquire()
try:
num = num + n # 15 = 9 + 6
num = num - n # 9
finally:
#修改完一定要釋放鎖
lock.release()
'''
#與上面代碼功能相同领迈,with lock可以自動(dòng)上鎖與解鎖
with lock:
num = num + n
num = num - n
if __name__ == "__main__":
t1 = threading.Thread(target=run, args=(6,))
t2 = threading.Thread(target=run, args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num =",num)
3.3 ThreadLocal
ThreadLocal 主要用來為各個(gè)線程管理其內(nèi)部數(shù)據(jù),它本身是一個(gè)全局變量惦费,但是每個(gè)線程卻可以利用它來保存屬于自己的私有數(shù)據(jù)薪贫,這些私有數(shù)據(jù)對其他線程也是不可見的刻恭。
import threading
num = 0
#創(chuàng)建一個(gè)全局的ThreadLocal對象
#每個(gè)線程有獨(dú)立的存儲(chǔ)空間
#每個(gè)線程對ThreadLocal對象都可以讀寫,但是互不影響
local = threading.local()
def run(x, n):
x = x + n
x = x - n
def func(n):
#每個(gè)線程都有l(wèi)ocal.x交洗,就是線程的局部變量
local.x = num
for i in range(1000000):
run(local.x, n)
print("%s-%d"%(threading.current_thread().name, local.x))
if __name__ == "__main__":
t1 = threading.Thread(target=func, args=(6,))
t2 = threading.Thread(target=func, args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num =",num)