一、線程的創建
1. 使用threading模塊創建線程(中階的併發編程)(本章使用的方法)
2. 利用第三方模塊(高階併發編程)
涉及到threading模塊創建線程分爲兩種模式:
(1)threading.Thread創建線程對象,指定兩個參數 target,args。
(2) 繼承threading.Thread,重寫run方法創建線程對象。
方式一:
threading.Thread(target=,args=)
target=要執行的函數名(要使用多線程實現執行函數)
args=要執行函數的參數(以元組的形式傳入)
time.sleep算是IO程序
import threading,time
def mission(end):
for i in range(end):
print(i)
time.sleep(0.5)
t1=threading.Thread(target=mission,args=(10,))
t2=threading.Thread(target=mission,args=(10,))
# 希望線程執行,需要將線程對象放到cpu的執行計劃(執行列表)
# 對象.start
# start並不代表執行,只能代表將任務交給了cpu,cpu什麼時候執行,由cpu說了算
t1.start()
t2.start()
方式二:繼承threading.Thread
import threading,time
class MyThread(threading.Thread):
def run(self):
for i in range(10):
print(i)
# time.sleep(0.5)
# print(threading.current_thread())
# print(threading.get_ident())
# print(threading.main_thread())
t1=MyThread()
t2=MyThread()
t1.start()
t2.start()
run方法纔是真正執行線程中任務的方法。如果直接調用run方法,那麼相當於讓當前任務串行執行。
t1.run()
t2.run()
二、線程的生命週期
1.新建:當新創建一個線程對象時,線程處於新建狀態
2.就緒:執行start方法之後,線程處於就緒狀態
3.運行:cpu將時間片分配給當前的線程對象,執行線程對應任務。
4.阻塞:因爲某些條件沒有滿足,處於等待的過程中,cpu會將時間片分給其他的線程。比如sleep(1),當睡1s之後,只能代表當前線程被重新加入到cpu的執行列表中。並不代表會馬上執行。
5.死亡:當線程run方法執行完畢,或者run方法中拋出了異常沒有被捕獲,程序意外終止。
三、線程的相關操作
1 threading.active_count() 當前活躍線程數量,(處於就緒之後,死亡之前)
print("當前活躍線程的數量",threading.active_count())
2.threading.enumerate() 返回一個列表,包含活躍的線程
li=threading.enumerate()
for i in li:
print(i)
3.threading.current_thread() 當前執行的線程
print(threading.current_thread())
4.threading.get_ident() 線程標誌,一個序號,獨一無二的序號
5.threading.main_thread() 返回執行解釋器的線程(主線程)。對於一個進程來說,只有一個主線程。 if __name__=="__main__":
print(threading.main_thread())
6. start 就緒,加入到cpu的執行列表中,等待執行
7. run(),當線程獲得時間片之後,會執行的方法
8. 線程對象.join(參數) 線程搶佔。B.join(參數) 在A 線程中,如果調用了B線程的join方法,B線程搶佔時間片
參數:搶佔的時間,不寫一直搶佔時間片到B執行結束
def mission():
print("休眠開始")
time.sleep(2)
print("休眠結束")
t=threading.Thread(target=mission)
t.start()
t.join()
print("主線程執行")
例子:修路的例子
def mission():
print("修路開始")
print("修路過程中....")
time.sleep(2)
print("修路結束")
t=threading.Thread(target=mission)
print("start之前ident=",t.ident)
t.start()
print(t.is_alive())
print("start之後ident=",t.ident)
t.name="過馬路的線程"
print("想要過馬路")
t.join()
print("路修好了,可以過馬路。。。。")
print(t.name)
print(t.is_alive())
9.name 線程中的一個私有屬性,線程名字。get和set ,被property化
10.ident 線程標誌 屬性,get_ident ===ident 被propery化 。沒有提供set方法,只有線程start啓動之後,纔有ident標誌
11. is_alive() 判斷是否存活
12.daemon 設置是否是守護線程(後臺線程)
守護線程
有一個唱歌,樂隊伴奏
兩種情況
(1)將樂隊伴奏設置爲非守護線程(默認,前臺線程)誰也不等誰(cpu採用異步模式對待每個線程)。唱歌的人唱完了,樂隊伴奏如果還沒有伴奏完畢,會繼續伴奏
(2)將樂隊線程設置爲守護線程。唱歌的人唱完了,樂隊伴奏不管有沒有演奏完畢,都不會繼續伴奏。如果將一個線程設置成守護線程:意味着告訴處理器,不用顧及當前的這個線程,當其他的非守護線程 退出的時候,守護線程也會跟着退出。
python中垃圾回收機制,使用守護線程
def music():
print("樂隊線程開始執行")
time.sleep(1)
print("樂隊持續在伴奏")
print("樂隊持續在伴奏")
print("樂隊持續在伴奏")
print("樂隊持續在伴奏")
print("樂隊持續在伴奏")
print("樂隊結束")
if __name__=="__main__":
mt=threading.Thread(target=music)
# 守護線程需要在start之前設置
mt.setDaemon(True)
mt.start()
print("開始唱歌")
print("唱歌中...")
print("唱歌結束")
四、線程同步
解決:多線程中出現的資源共享問題。
1. 併發修改出現問題
票就是共享資源
ticket=100
def buy_ticket():
global ticket
while ticket>0:
time.sleep(0.5)
print("{}搶到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
t1=threading.Thread(target=buy_ticket)
t1.name="張三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
2. 線程鎖
希望在同一個時間點內,一片共享資源只希望被一個線程訪問。
方式:lock=threading.Lock()
加鎖:lock.acquire()
解鎖:lock.release()
第一次解決: while循環可能同時進入了其他兩個線程
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
while ticket>0:
lock.acquire()
# time.sleep(0.5)
print("{}搶到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="張三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
第二次 問題原因:張三進入之後加鎖,循環將所有的排票都搶完畢
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
lock.acquire()
while ticket>0:
# time.sleep(0.5)
print("{}搶到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="張三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
第三次: 沒有人解最後鎖
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
while True:
lock.acquire()
if ticket>0:
# time.sleep(1)
print("{}搶到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
else:
break
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="張三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
第四次
ticket=100
lock=threading.Lock()
def buy_ticket():
global ticket
while True:
try:
lock.acquire()
if ticket>0:
time.sleep(0.2)
print("{}搶到了第{}票".format( threading.current_thread().name,ticket))
ticket-=1
else:
break
finally:
lock.release()
t1=threading.Thread(target=buy_ticket)
t1.name="張三"
t2=threading.Thread(target=buy_ticket)
t2.name="李四"
t3=threading.Thread(target=buy_ticket)
t3.name="王五"
t1.start()
t2.start()
t3.start()
五、死鎖
死鎖的定義:當兩個或者多個線程同時擁有自己的資源,又互相等待對方的資源,導致程序永遠先入僵持狀態
共有兩把鎖
A----鎖1-----希望擁有鎖2
B----鎖2-----希望擁有鎖1
多線程併發訪問數據的時候,要在共享資源上加鎖。如果加的鎖不只一把,可能會出現死鎖
import threading,time
lock1=threading.Lock()
lock2=threading.Lock()
def mission(l1,l2): #l1,l2代表傳入的兩把鎖
l1.acquire()
print("{}獲得了{}".format(threading.current_thread().name,id(l1)))
time.sleep(1)
l2.acquire()
l1.release()
l2.release()
a=threading.Thread(target=mission,args=(lock1,lock2))
b=threading.Thread(target=mission,args=(lock2,lock1))
a.start()
b.start()
六、通知和等待
搶票,一個資源(生產者),多個線程共享。
生產者和消費者。
使用from threading import Condition 的鎖,不僅有獲取和釋放的方法
還有:
wait :等待, 會釋放當前線程佔用的鎖(跟time.sleep不一樣,sleep,不釋放鎖)
notifyall:通知所有等待的線程加入cpu執行列表
notify方法:任選一個等待線程,加入cpu執行列表
需求:
生產者:生產商品,讓商品+1,定義一個列表倉庫,每次列表中加入一個元素,當做生產一個商品
消費者:消費商品,讓商品-1,讓列表中元素被刪除
當供過於求:生產者生產太快,商品在倉庫中達到3件之後,生產者不能再生產,需要被阻塞。被喚醒的時機:只有要商品被消費了
求大於供:消費者消費太快,商品在倉庫中0個,消費者就不能再消費,需要被阻塞。被喚醒的時機:只要有商品被生產了
from threading import Condition
lock=threading.Condition()
def produce(li):
i=0
while True:
try:
lock.acquire()
if len(li)==3:
print("倉庫已滿,生產阻塞")
lock.wait()
else:
li.append("商品{}".format(i))
i+=1
print("生產了{}商品".format(i))
lock.notify_all()
finally:
lock.release()
def consume(li):
while True:
try:
lock.acquire()
if len(li)==0:
print("倉庫已空,消費阻塞")
lock.wait()
else:
print("消費了{}商品".format(li.pop(0)))
lock.notify_all()
finally:
lock.release()
li=[]
t1=threading.Thread(target=produce,args=(li,))
t2=threading.Thread(target=consume,args=(li,))
t1.start()
t2.start()
七、隊列(線程)
隊列數據類型,內部實現了鎖的機制,隊列多用於多線程的併發
隊列分爲三種:
①先進先出隊列:隊列
②先進後出隊列:堆棧
③優先隊列:按照優先級出隊列
(1) 先進先出隊列
queue.Queue(size):size=0或者負數,表示無限容量,如果size有值,代表最大容量
q=queue.Queue(3)
通過q.qsize() 返回隊列中元素的格式
print(q.qsize())
q.empty隊列是否爲空
print(q.empty())
q.full() 判斷隊列是否已滿
print(q.full())
q.put向隊列中添加元素
item:要添加的元素
block:繼續向隊列中添加元素的時候,如果隊列已滿,put方法是否是處於阻塞狀態(默認True)block如果=False,當隊列已滿的時候 ,再繼續添加元素,則會報錯。
timeout:隊列已滿,如果在指定的時間內(單位:秒),仍然無法添加元素,則會產生異常
q.put("hello")
q.put("world",block=False)
put_nowait 代表只要隊列已滿,put函數執行的時候會報錯。
q.put_nowait(item) # 等價於put(item,block=False)
q.get() 向外取元素(規則就是先進先出)
q.put("python")
print(q.get())
print(q.get())
print(q.get())
print(q.get())
如果隊列是空隊列,繼續向外get元素,get方法是默認的阻塞函數。用法跟put中一樣。
q.get(block=False)
q.get_nowait()====q.get(block=False)
(2) 先進後出,堆棧
lq=queue.LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())
print(lq.get())
print(lq.get())
print(lq.get())
while not q.empty():
print(q.get())
(3) 優先隊列
不是按照傳統隊列的先進先出,或者後進先出,而是根據隊列的優先級別進行排列。進的時候,正常進入 ,出的時候是按照優先級別出。優先級隊列中的元素必須支持元素之間的比較。
print("abc"<"bcd")
print((1,2,4)<(3,4))
q=queue.PriorityQueue()
q.put("clock")
q.put("banana")
q.put("egg")
q.put("apple")
while not q.empty():
print(q.get())
q.put((1,2,3))
q.put((2,2,3))
q.put((3,2,3))
q.put((-1,2,3))
while not q.empty():
print(q.get())
q=queue.PriorityQueue()
q.put((2,"clock"))
q.put((1,"banana"))
q.put((3,"egg"))
q.put((4,"apple"))
# q.put("a")
while not q.empty():
print(q.get())
應用隊列實現生產者和消費者的例子
import queue
import threading
def produce(q):
i=1
while True:
q.put(i)
print("生產商品{}".format(i))
i+=1
time.sleep(0.5)
def consume(q,name):
while True:
print("{}消費了{}".format(name,q.get()))
time.sleep(0.1)
# 隊列本身創建的時候就有容量的設置
q=queue.Queue(3)
t1=threading.Thread(target=produce,args=(q,))
t2=threading.Thread(target=consume,args=(q,"tom"))
t3=threading.Thread(target=consume,args=(q,"jerry"))
t1.start()
t2.start()
t3.start()