多任務(wù)
-
多任務(wù)含義:
生活:一邊聽(tīng)歌,一邊跳舞
電腦:同時(shí)運(yùn)行多個(gè)程序寂恬,如:qq续誉,微信莱没,陌陌初肉,瀏覽器
-
并發(fā)和并行
并發(fā):任務(wù)數(shù)大于核心數(shù),通過(guò)操作系統(tǒng)調(diào)度算法實(shí)現(xiàn)多個(gè)任務(wù)“同時(shí)”執(zhí)行饰躲,實(shí)際上通過(guò)快速切換任務(wù)牙咏,看上去是一起執(zhí)行的,時(shí)間片輪轉(zhuǎn)方式
并行:任務(wù)數(shù)小于核心數(shù)嘹裂,任務(wù)是真正一起執(zhí)行
-
進(jìn)程:正在運(yùn)行的一個(gè)程序我們可以說(shuō)是一個(gè)進(jìn)程 妄壶,是系統(tǒng)進(jìn)行資源分配和調(diào)用的獨(dú)立單元,每
一個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間和系統(tǒng)資源
程序:運(yùn)行的應(yīng)用程序稱之為進(jìn)程寄狼。當(dāng)一個(gè)程序不運(yùn)行的時(shí)候我們稱之為程序丁寄,當(dāng)程序運(yùn)行起來(lái)就是一個(gè)進(jìn)程。通俗的來(lái)說(shuō)不運(yùn)行的時(shí)候就是程序泊愧,運(yùn)行起來(lái)就是進(jìn)程伊磺。程序只有一個(gè),但是進(jìn)程可以有多個(gè)
-
線程:線程是進(jìn)程中的一條執(zhí)行線路或者流程删咱,程序執(zhí)行的最小單位屑埋,線程是任務(wù)調(diào)度的最小單 位。
- 由于進(jìn)程是資源擁有者痰滋,創(chuàng)建摘能、撤消與切換存在較大的內(nèi)存開(kāi)銷,因此需要引入輕型進(jìn)程 即線程敲街,進(jìn)程是資源分配的最小單位,線程是 CPU 調(diào)度的最小單位(程序真正執(zhí)行的時(shí)候調(diào) 用的是線程)每一個(gè)進(jìn)程中至少有一個(gè)線程
進(jìn)程和線程的關(guān)系:一個(gè)進(jìn)程中可以有一到多個(gè)線程团搞。一個(gè)線程只屬于一個(gè)進(jìn)程。一個(gè)進(jìn)程中的多個(gè)線程是一種 競(jìng)爭(zhēng)關(guān)系
創(chuàng)建線程
?
import time
import threading
#一邊聽(tīng)歌多艇,一邊下載歌曲
def listen():
for i in range(1,6):
print("正在聽(tīng)歌")
time.sleep(1)
def down_load():
for i in range(1,6):
print("正在下載歌曲")
time.sleep(1)
# down_load()
# listen()
if __name__ == '__main__':
#創(chuàng)建多線程
t1 = threading.Thread(target=down_load)
t2 = threading.Thread(target=listen)
#開(kāi)啟線程
t1.start()
t2.start()
#執(zhí)行順序:程序運(yùn)行時(shí)逻恐,代碼從上向下走,走了if __name__ == '__main__':創(chuàng)建了兩條線程墩蔓,我們稱之為子線程梢莽,程序運(yùn)行時(shí)的線程我們稱之為主線程
#子線程根據(jù)target=xxx,開(kāi)始執(zhí)行指定的函數(shù)
-
線程注意點(diǎn)
線程合適開(kāi)啟奸披,何時(shí)結(jié)束 1.子線程何時(shí)開(kāi)啟昏名,何時(shí)運(yùn)行 當(dāng)調(diào)用start()時(shí),開(kāi)啟線程阵面,再運(yùn)行線程中的代碼 2.子線程何時(shí)結(jié)束 子線程把target指向的函數(shù)中的語(yǔ)句執(zhí)行完畢轻局,當(dāng)前子線程結(jié)束 3.主線程何時(shí)結(jié)束 所有子線程執(zhí)行完畢后洪鸭,主線程才結(jié)束 4.查看線程數(shù)量:threading.enumerate(),可以列出當(dāng)前運(yùn)行的所有線程
-
查看線程執(zhí)行情況
import threading import time def test1(): for i in range(1,6): time.sleep(1) print("--子線程1--%d"%i) print("子線程1中查看線程情況",threading.enumerate()) def test2(): for i in range(1,6): time.sleep(1) print("--子線程2--%d"%i) print("子線程2中查看線程情況",threading.enumerate()) def main(): print("創(chuàng)建線程之前的線程情況",threading.enumerate()) #創(chuàng)建線程 t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) time.sleep(0.1) print("創(chuàng)建線程之后的線程情況", threading.enumerate()) #開(kāi)啟線程 t1.start() t2.start() time.sleep(1) print("調(diào)用start之后的線程情況", threading.enumerate()) time.sleep(6) print("等待子線程執(zhí)行結(jié)束后的線程情況", threading.enumerate()) if __name__ == '__main__': main()
-
args傳遞參數(shù)
- 給函數(shù)傳遞參數(shù),使用線程關(guān)鍵字args = ()進(jìn)行傳遞參數(shù)仑扑,傳遞的參數(shù)是一個(gè)元組
import time import threading #一邊聽(tīng)歌览爵,一邊下載歌曲 def listen(num): for i in range(1,num): print("正在聽(tīng)歌%d"%i) time.sleep(1) def down_load(num): for i in range(1,num): print("正在下載歌曲%d"%i) time.sleep(1) if __name__ == '__main__': #創(chuàng)建多線程 t1 = threading.Thread(target=down_load,args=(10,)) t2 = threading.Thread(target=listen,args=(10,)) #開(kāi)啟線程 t1.start() t2.start()
-
join方法
- 功能:當(dāng)前線程執(zhí)行完后其他線程才會(huì)繼續(xù)執(zhí)行
def main(): #創(chuàng)建多線程 t1 = threading.Thread(target=down_load,args=(5,)) t2 = threading.Thread(target=listen,args=(5,)) #開(kāi)啟線程 t1.start() t1.join() #t1執(zhí)行完之后,t2和主線程才會(huì)執(zhí)行結(jié)束 t2.start() # t2.join() #在t1镇饮,t2執(zhí)行完后蜓竹,再繼續(xù)執(zhí)行主線程 if __name__ == '__main__': main() print("程序執(zhí)行結(jié)束了")
setDaemon() 方法
- setDaemon()將當(dāng)前線程設(shè)置成守護(hù)線程來(lái)守護(hù)主線程:
-當(dāng)主線程結(jié)束后,守護(hù)線程也就結(jié)束储藐,不管是否執(zhí)行完成 俱济,即主線程結(jié)束后不等待子線程,立即結(jié)束
-應(yīng)用場(chǎng)景:qq 多個(gè)聊天窗口钙勃,就是守護(hù)線程
注意:需要在子線程開(kāi)啟的時(shí)候設(shè)置成守護(hù)線程蛛碌,否則無(wú)效
import time
import threading
#一邊聽(tīng)歌,一邊下載歌曲
def listen(num):
for i in range(1,num):
print("正在聽(tīng)歌%d"%i)
time.sleep(1)
def down_load(num):
for i in range(1,num):
print("正在下載歌曲%d"%i)
time.sleep(0.5)
def main():
#創(chuàng)建多線程
t1 = threading.Thread(target=down_load,args=(5,))
t2 = threading.Thread(target=listen,args=(5,))
# t1.setDaemon(True)
t2.setDaemon(True)
#開(kāi)啟線程
t1.start()
t2.start()
if __name__ == '__main__':
main()
print("程序執(zhí)行結(jié)束了")
-
threading模塊提供的方法
- threading辖源。currentThread():返回當(dāng)前的線程變量
- threading.enumerate():可以列出當(dāng)前正在運(yùn)行的所有線程蔚携,正在運(yùn)行的線程:?jiǎn)?dòng)后,結(jié)束前克饶,不包括啟動(dòng)前和終止后
- threading.activeCount()返回正在運(yùn)行的線程數(shù)量酝蜒,與len(threading.enumerate)有相同的結(jié)果
- 線程.getName():獲取線程名稱
- 線程.setName():設(shè)置線程名稱
- 線程.is_alive():判斷線程存活狀態(tài)
def listen(num): for i in range(1,num): print("正在聽(tīng)歌%d"%i) time.sleep(1) def down_load(num): for i in range(1,num): print("正在下載歌曲%d"%i) time.sleep(0.5) def main(): #創(chuàng)建多線程 t1 = threading.Thread(target=down_load,args=(5,)) t2 = threading.Thread(target=listen,args=(5,)) print("t1線程開(kāi)啟之前的狀態(tài)",t1.is_alive()) #False t1.setName("黃志") print("正在運(yùn)行的線程數(shù)量:",threading.activeCount()) #1 #開(kāi)啟線程 t1.start() print("當(dāng)前線程:",threading.currentThread()) #MainThread print(threading.enumerate()) #列表中2個(gè)線程 print("正在運(yùn)行的線程數(shù)量:", threading.activeCount()) #2 t2.start() print("當(dāng)前線程:", threading.currentThread()) #MainThread print(threading.enumerate()) #列表中3個(gè)線程 print("正在運(yùn)行的線程數(shù)量:", threading.activeCount()) #3 if __name__ == '__main__': main() print("程序執(zhí)行結(jié)束了") print("正在運(yùn)行的線程數(shù)量:", threading.activeCount()) #3
使用繼承方式開(kāi)啟線程
定義一個(gè)類繼承threading.Thread
-
復(fù)寫(xiě)父類的run方法
import threading import time class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = f"I'm {self.name} @{i}" print(msg) def test1(): for i in range(5): #創(chuàng)建線程 t = MyThread() #開(kāi)啟線程 t.start() if __name__ == '__main__': test1()
線程之間共享全局變量
def work1():
g_num.append(44)
print(f"--in work1,g_num is {g_num}") #[11,22,33,44]
def work2():
print(f"--in work2,g_num is {g_num}") #[11,22,33,44]
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
g_num = [11,22,33]
t1.start()
time.sleep(1)
t2.start()
- 總結(jié):在一個(gè)進(jìn)程內(nèi)的所有線程共享全局變量,很方便在多個(gè)線程共享數(shù)據(jù)
- 缺點(diǎn):多線程對(duì)全局變量變量隨意修改可能會(huì)造成數(shù)據(jù)混亂(線程非安全)
多線程開(kāi)發(fā)可能會(huì)遇到的問(wèn)題
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num+=1
print(f"--in work1,g_num is {g_num}")
def work2(num):
global g_num
for i in range(num):
g_num+=1
print(f"--in work2,g_num is {g_num}")
t1 = threading.Thread(target=work1,args=(100,))
t2 = threading.Thread(target=work2,args=(100,))
t1.start()
t2.start()
#t1,t2兩個(gè)線程都要對(duì)全局變量g_num進(jìn)行加1運(yùn)算彤路,t1,t2各自對(duì)g_num加100次秕硝,那么最終結(jié)果應(yīng)該是200
#但是由于是多線程同時(shí)操作,可能會(huì)出現(xiàn)以下問(wèn)題
#在g_num=0,t1先取得g_num=0,此時(shí)系統(tǒng)將t1調(diào)為“sleeping”狀態(tài)洲尊,把t2轉(zhuǎn)換成“running”狀態(tài)远豺,t2也獲取g_num=0,
#然后t2對(duì)獲取到的值進(jìn)行加1操作并賦值給g_num,使得g_num=1
#然后系統(tǒng)將將t2調(diào)為“sleeping”狀態(tài),把t1轉(zhuǎn)換成“running”狀態(tài)坞嘀,線程t1把獲取到的g_num=0加1后賦值給g_nun,使得g_num=1
#這樣導(dǎo)致雖然t1和t2都對(duì)g_num加1躯护,但結(jié)果是1
同步和異步
- 同步:同步就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行運(yùn)行丽涩,同是“協(xié)同”的意思
- 異步:一個(gè)異步調(diào)用過(guò)程發(fā)出后棺滞,調(diào)用者在沒(méi)得到結(jié)果之前可以進(jìn)行后續(xù)操作。同步和異步是對(duì)應(yīng)的矢渊,兩個(gè)線程要么同步继准,要么異步
互斥鎖
當(dāng)多個(gè)線程幾乎同時(shí)修改一個(gè)共享數(shù)據(jù)的時(shí)候,需要進(jìn)行同步控制矮男,線程同步能夠保證多個(gè) 線程安全的訪問(wèn)競(jìng)爭(zhēng)資源(全局內(nèi)容)移必,最簡(jiǎn)單的同步機(jī)制就是使用互斥鎖
互斥鎖是資源的一個(gè)引用狀態(tài):鎖定/非鎖定
-
使用過(guò)程:某個(gè)線程要更改 共享數(shù)據(jù)時(shí),先將其鎖定毡鉴,此時(shí)資源的狀態(tài)為鎖定狀態(tài)崔泵,其他線程就不能更改秒赤,直到該線程將 資源狀態(tài)改為非鎖定狀態(tài),也就是釋放資源憎瘸,其他的線程才能再次鎖定資源
import time g_num = 0 def work1(num): global g_num for i in range(num): mutex.acquire() #鎖定 g_num+=1 mutex.release() #釋放 def work2(num): global g_num for i in range(num): mutex.acquire() # 鎖定 g_num += 1 mutex.release() # 釋放 mutex = threading.Lock() t1 = threading.Thread(target=work1,args=(1000000,)) t2 = threading.Thread(target=work2,args=(1000000,)) t1.start() t2.start() while len(threading.enumerate())!=1: time.sleep(1) print(f"--in work2,g_num is {g_num}")
-
鎖的好處:
- 確保了某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾完整地執(zhí)行
-
鎖的壞處:
- 阻止了多線程并發(fā)執(zhí)行入篮,包含鎖的某段代碼實(shí)際上只能以單線程模式執(zhí)行,效率就大大地 下降了
- 由于可以存在多個(gè)鎖幌甘,不同的線程持有不同的鎖潮售,并試圖獲取對(duì)方持有的鎖時(shí),可能會(huì)造成死鎖
死鎖
-
在多個(gè)線程共享資源的時(shí)候含潘,如果兩個(gè)線程同時(shí)占有一部分資源饲做,并且同時(shí)等待對(duì)方的資源,就會(huì)造成死鎖現(xiàn)象遏弱,盡管死鎖很少發(fā)生,但是一旦發(fā)生就會(huì)造成應(yīng)用程序的停止相應(yīng)
import time def test1(): #lock1上鎖 lock1.acquire() print("--test1開(kāi)始執(zhí)行--") time.sleep(1) lock2.acquire() print("--test1執(zhí)行結(jié)束--") lock2.release() #lock2解鎖 lock1.release()# lock1解鎖 def test2(): #lock2上鎖 lock2.acquire() print("--test2開(kāi)始執(zhí)行--") # lock1上鎖 time.sleep(1) lock1.acquire() print("--test2執(zhí)行結(jié)束--") lock1.release() lock2.release() lock1 = threading.Lock() lock2 = threading.Lock() if __name__ == '__main__': t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() t2.start()
生產(chǎn)者和消費(fèi)者
-
隊(duì)列:Python 的 Queue 模塊中提供了同步的塞弊、線程安全的隊(duì)列類漱逸,包括 FIFO(先入先出)隊(duì)列 Queue, LIFO(后入先出)隊(duì)列 LifoQueue游沿,和優(yōu)先級(jí)隊(duì)列 PriorityQueue饰抒。這些隊(duì)列都實(shí)現(xiàn)了鎖原語(yǔ) (可以理解為原子操作,即要么不做诀黍,要么就做完)袋坑,能夠在多線程中直接使用∶泄矗可以使用 隊(duì)列來(lái)實(shí)現(xiàn)線程間的同步
from queue import Queue q = Queue(maxsize=3) q.put("黃志") print(q.empty()) #False q.put("古印") q.put("王濤") print(q.full()) # q.put("楊俊") print(q.get()) print(q.get()) print(q.get()) print(q.get()) # print(q.get(timeout=3)) # print(q.get_nowait()) print(q.qsize())
-
生產(chǎn)者和消費(fèi)者
- 生產(chǎn)者:生成數(shù)據(jù)的線程
- 消費(fèi)者:處理數(shù)據(jù)的線程
- 目的:平衡生產(chǎn)者和消費(fèi)者的工作能力來(lái)提高程序的整理處理數(shù)據(jù)的速度
import time import queue def producer(name):#生產(chǎn)者 count = 1 while True: print("%s生產(chǎn)了包子%d"%(name,count)) q.put(count) count+=1 time.sleep(1) print("包子總數(shù):",q.qsize()) def costom(name):#消費(fèi)者 while True: print("%s吃了包子%s"%(name,q.get())) time.sleep(0.5) if __name__ == '__main__': q = queue.Queue(maxsize=5) t1 = threading.Thread(target=producer,args=("黃志",)) t2 = threading.Thread(target=costom,args=("吃貨古",)) t3 = threading.Thread(target=costom, args=("吃貨劉",)) t1.start() t2.start() t3.start()
-
為什么使用生產(chǎn)者消費(fèi)者模式
-
在線程世界里枣宫,生產(chǎn)者就是生產(chǎn)數(shù)據(jù)的線程,消費(fèi)者就是消費(fèi)數(shù)據(jù)的線程吃环。在多線程開(kāi)發(fā)當(dāng)
中也颤,如果生產(chǎn)者處理速度很快,而消費(fèi)者處理速度很慢郁轻,那么生產(chǎn)者就必須等待消費(fèi)者處理
完翅娶,才能繼續(xù)生產(chǎn)數(shù)據(jù)。同樣的道理好唯,如果消費(fèi)者的處理能力大于生產(chǎn)者竭沫,那么消費(fèi)者就必
須等待生產(chǎn)者。為了解決這個(gè)問(wèn)題于是引入了生產(chǎn)者和消費(fèi)者模式
-
-
什么是生產(chǎn)者消費(fèi)者模式
- 生產(chǎn)者消費(fèi)者模式是通過(guò)一個(gè)容器來(lái)解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問(wèn)題骑篙。生產(chǎn)者和消費(fèi)者彼 此之間不直接通訊蜕提,而通過(guò)阻塞隊(duì)列來(lái)進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)
者處理替蛉,直接扔給阻塞隊(duì)列贯溅,消費(fèi)者不找生產(chǎn)者要數(shù)據(jù)拄氯,而是直接從阻塞隊(duì)列里取,阻塞隊(duì) 列就相當(dāng)于一個(gè)緩沖區(qū)它浅,平衡了生產(chǎn)者和消費(fèi)者的處理能力
- 生產(chǎn)者消費(fèi)者模式是通過(guò)一個(gè)容器來(lái)解決生產(chǎn)者和消費(fèi)者的強(qiáng)耦合問(wèn)題骑篙。生產(chǎn)者和消費(fèi)者彼 此之間不直接通訊蜕提,而通過(guò)阻塞隊(duì)列來(lái)進(jìn)行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費(fèi)