第十七天
Python多任務(wù)
其實(shí)锅纺,多任務(wù)就是操作系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù)攀涵。比如說我們可以一邊寫代碼铣耘,一邊聽歌洽沟,一邊上網(wǎng)以故,一邊和別人聊天,這其實(shí)就是多任務(wù)的體現(xiàn)裆操。
多任務(wù)的執(zhí)行方式有兩種:
1怒详、并發(fā):交替執(zhí)行炉媒,是假的多任務(wù),指的是任務(wù)數(shù)多于cpu核數(shù)昆烁,通過操作系統(tǒng)的各種任務(wù)調(diào)度算法吊骤,處理完一個(gè)任務(wù)處理下一個(gè)(時(shí)間輪片執(zhí)行哪個(gè)執(zhí)行哪個(gè)),然后實(shí)現(xiàn)用多個(gè)任務(wù)“一起”執(zhí)行静尼。其實(shí)不是一起執(zhí)行的白粉,只是處理的速度快,看似一起執(zhí)行鼠渺。
2鸭巴、并行:指的是任務(wù)數(shù)小于等于cpu核數(shù),即任務(wù)真的是一起執(zhí)行的拦盹,同時(shí)執(zhí)行鹃祖,是真的多任務(wù),cpu一個(gè)一個(gè)單獨(dú)執(zhí)行普舆。
1恬口、線程
線程可以簡(jiǎn)單理解為同一進(jìn)程中有多個(gè)計(jì)數(shù)器,每個(gè)線程的執(zhí)行時(shí)間不確定沼侣,線程是操作系統(tǒng)調(diào)度執(zhí)行的最小單位祖能。
Python中使用線程,需要引入thread模塊蛾洛,但是這個(gè)模塊在Python 2 中屬于正承旧保可用狀態(tài),但在 Python 3 中處于即將廢棄的狀態(tài)雅潭,雖然還可以用揭厚,但包名被改為 _thread,在Python3中一般使用threading模塊扶供。
1.1筛圆、單線程執(zhí)行
import time
import threading
def dance():
print('小張?jiān)谔?)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=dance)
t.start() #啟動(dòng)線程執(zhí)行任務(wù)調(diào)用start的時(shí)候start自動(dòng)調(diào)用run方法
time.sleep(1) # 1s后執(zhí)行下一次循環(huán)
1.2、多線程執(zhí)行
import time
import threading
def tiao(): # 子線程
for i in range(5):
print('小李在跳舞')
time.sleep(1)
def sing(): # 子線程
for i in range(5):
print('小張?jiān)诔?)
time.sleep(1)
def main(): # 主線程
t1 = threading.Thread(target=tiao) #target=函數(shù)--->指向函數(shù)
t2 = threading.Thread(target=sing) #target=函數(shù)--->指向函數(shù)
t1.start() #啟動(dòng)線程執(zhí)行任務(wù)
t2.start() #啟動(dòng)線程執(zhí)行任務(wù)
if __name__ == '__main__':
main()
注意:
1椿浓、調(diào)用thread太援,不會(huì)創(chuàng)建線程,只是創(chuàng)建一個(gè)對(duì)象扳碍;
2提岔、當(dāng)調(diào)用thread創(chuàng)建出來的實(shí)例對(duì)象的start方法的時(shí)候,才會(huì)創(chuàng)建線程以及讓這個(gè)線程開始運(yùn)行笋敞;
3碱蒙、主線程會(huì)等待子線程結(jié)束之后才會(huì)結(jié)束,主線程一死,子線程也會(huì)死赛惩。線程的調(diào)度是隨機(jī)的哀墓,并沒有先后順序。
1.3喷兼、枚舉函數(shù)enumerate的用法
enumerate()是python的內(nèi)置函數(shù)篮绰,在字典上是枚舉、列舉的意思季惯。
用途:用于將一個(gè)可遍歷的數(shù)據(jù)對(duì)象(如列表吠各、元組或字符串)組合為一個(gè)索引序列,同時(shí)列出數(shù)據(jù)和數(shù)據(jù)下標(biāo)勉抓,一般用在 for 循環(huán)當(dāng)中走孽,通過threading.enumerate()就可以獲取線程列表。
import time
import threading
def tiao():
for i in range(5):
print('小李在跳舞')
time.sleep(1)
def sing():
for i in range(5):
print('小張?jiān)诔?)
time.sleep(1)
def main():
t1 = threading.Thread(target=tiao)
t2 = threading.Thread(target=sing)
t1.start()
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate())<=1:
break
time.sleep(1)
if __name__ == '__main__':
main()
2琳状、多線程-共享全局變量
2.1磕瓷、多線程-共享全局變量
假設(shè)兩個(gè)線程t1和t2都要對(duì)全局變量g_num(默認(rèn)是0)進(jìn)行加1運(yùn)算
import threading
import time
g_num = 0
def test1():
global g_num
for i in range(100):
g_num += 1
print('test1方法,g_num的最后結(jié)果是%d'%(g_num))
def test2():
global g_num
for i in range(100):
g_num += 1
print('test2方法,g_num的最后結(jié)果是%d'%(g_num))
if __name__ == '__main__':
print('線程創(chuàng)建之前,g_num的值是%d'%(g_num))
t1 = threading.Thread(target=test1)
t1.start()
t2 = threading.Thread(target=test2)
t2.start()
print('兩個(gè)線程創(chuàng)建之后,g_num的值是%d' %(g_num))
# test1方法,g_num的最后結(jié)果是125
# test2方法,g_num的最后結(jié)果是126
# test2方法,g_num的最后結(jié)果是127
# test2方法,g_num的最后結(jié)果是128
# test1方法,g_num的最后結(jié)果是129
# test1方法,g_num的最后結(jié)果是130
# test1方法,g_num的最后結(jié)果是131
我們觀察到在test1中參雜這test2,也就說明線程是對(duì)全局變量隨意修改可能造成多線程之間對(duì)全局變量的混亂念逞、甚至數(shù)據(jù)的不正確(即線程非安全)困食,也就是說如果多個(gè)線程同時(shí)對(duì)同一個(gè)全局變量操作,會(huì)出現(xiàn)資源競(jìng)爭(zhēng)問題翎承,從而數(shù)據(jù)結(jié)果會(huì)不正確硕盹。我們可以在一個(gè)線程執(zhí)行的時(shí)候time.sleep(1)來保證線程的正常執(zhí)行(不一定),好處就是在一個(gè)進(jìn)程內(nèi)的所有線程共享全局變量叨咖,很方便在多個(gè)線程間共享數(shù)據(jù)瘩例。
2.2、多線程-共享全局變量-args參數(shù)
其實(shí)就是我們?cè)诙x線程target的時(shí)候甸各,可以傳遞參數(shù)去子線程垛贤。
import threading
import time
g_nums = [1,2,3,4,5]
def test1(temp):
temp.append(44)
print('test1方法,temp的最后結(jié)果是%s'% str(temp))
def test2(temp):
print('test2方法,temp的最后結(jié)果是%s'% str(temp))
if __name__ == '__main__':
print('線程創(chuàng)建之前,g_nums的值是%s'% str(g_nums))
t1 = threading.Thread(target=test1,args=(g_nums,))
t1.start()
time.sleep(1)
t2 = threading.Thread(target=test2,args=(g_nums,))
t2.start()
print('兩個(gè)線程創(chuàng)建之后,g_nums的值是%s' % str(g_nums))
3、線程同步
我們前面說了趣倾,多線程開發(fā)聘惦,難免會(huì)遇見線程間的資源爭(zhēng)奪問題,其實(shí)線程間出現(xiàn)問題的原因歸根到底就是線程1對(duì)某個(gè)數(shù)據(jù)操作的時(shí)候還沒有結(jié)束的時(shí)候儒恋,另一個(gè)線程其實(shí)已經(jīng)獲得了時(shí)間輪片善绎,就如同張三轉(zhuǎn)賬給李四,張三的money已經(jīng)扣除诫尽,但是李四沒有收到禀酱,在數(shù)據(jù)庫中我們用事務(wù)來解決,那么在我們的python代碼中牧嫉,如何同步呢剂跟?就是加鎖,加互斥鎖。
3.1浩聋、互斥鎖
多個(gè)線程對(duì)同一數(shù)據(jù)操作的時(shí)候观蜗,需要進(jìn)行同步控制臊恋,保證多個(gè)線程安全訪問競(jìng)爭(zhēng)資源衣洁,這就是互斥鎖同步機(jī)制。
互斥鎖兩個(gè)狀態(tài):鎖定抖仅、非鎖定坊夫。
也就是線程1對(duì)數(shù)據(jù)操作時(shí),然后加鎖撤卢,此時(shí)資源狀態(tài)為鎖定环凿,其它線程無法對(duì)數(shù)據(jù)進(jìn)行操作,除非線程1對(duì)數(shù)據(jù)操作完畢放吩,將資源狀態(tài)改為非鎖定智听,其他線程才可以鎖定該資源,對(duì)該資源操作渡紫〉酵疲互斥鎖保證每次只有一個(gè)線程對(duì)資源操作,從而保證多線程情況下數(shù)據(jù)的正確性惕澎。
import threading
import time
g_num = 0
# 創(chuàng)建一個(gè)互斥鎖莉测,默認(rèn)沒有上鎖
mutex = threading.Lock()
def test1():
global g_num
mutex.acquire() #上鎖
for i in range(100):
g_num += 1
print('test1方法,g_num的最后結(jié)果是%d'%(g_num))
mutex.release() #解鎖
def test2():
global g_num
mutex.acquire() # 上鎖
for i in range(100):
g_num += 1
print('test2方法,g_num的最后結(jié)果是%d'%(g_num))
mutex.release() # 解鎖
if __name__ == '__main__':
print('線程創(chuàng)建之前,g_num的值是%d'%(g_num))
t1 = threading.Thread(target=test1)
t1.start()
t2 = threading.Thread(target=test2)
t2.start()
print('兩個(gè)線程創(chuàng)建之后,g_num的值是%d' %(g_num))
注意:線程通過mutex.acquire()上鎖的時(shí)候,那么之前沒有上鎖唧喉,那么此時(shí)上鎖成功捣卤,上鎖之前如果已經(jīng)上鎖,那么此時(shí)會(huì)堵塞八孝,直到這個(gè)鎖解開董朝。
3.2、死鎖(了解)
線程間共享多個(gè)資源的時(shí)候干跛,如果兩個(gè)線程分別占有一部分資源并且同時(shí)等待對(duì)方的資源益涧,那么就會(huì)形成死鎖。
造成死鎖的原因可以概括成三句話:
1.不同線程同時(shí)占用自己鎖資源
2.這些線程都需要對(duì)方的鎖資源
3.這些線程都不放棄自己擁有的資源
import threading
import time
mutexA = threading.Lock()
mutexB = threading.Lock()
class MyThreadA(threading.Thread):
def run(self):
mutexA.acquire() #對(duì)mutexA上鎖
print('------MyThreadA---start------')
time.sleep(1) #等待一秒驯鳖,此時(shí)mutexB上鎖
mutexB.acquire() #這個(gè)時(shí)候堵塞 mutexB已經(jīng)上鎖過了
print('------MyThreadA---end------')
mutexB.release() #不釋放鎖 死鎖
mutexA.release()
class MyThreadB(threading.Thread):
def run(self):
mutexB.acquire() #對(duì)mutexB上鎖
print('------MyThreadB---start------')
time.sleep(1) #等待一秒闲询,此時(shí)mutexA上鎖
mutexA.acquire() #這個(gè)時(shí)候堵塞 mutexA已經(jīng)上鎖過了
print('------MyThreadB---end------')
mutexA.release() #不釋放鎖 死鎖
mutexB.release()
if __name__ == '__main__':
t1 = MyThreadA()
t2 = MyThreadB()
t1.start()
t2.start()