Python-線程

一、線程的創建

  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()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章