Python基礎之多線程
1.進程與線程
進程:一個正在執行的程序,稱爲一個進程
線程:進程執行的最小單位,可以保證進程的正常執行。一個進程中至少含有一個線程,該線程稱爲主線程
2.Python中的線程
python中使用_thread
或threading
模塊對線程進程處理。其中threading
是對_thread
的再次整合,所以在Python3中建議使用threading
模塊。
導入threading
模塊
import threading
獲取主線程
# 獲取主線程
print(threading.main_thread())
# <_MainThread(MainThread, started 5748)>啓動狀態
創建子線程
# 線程執行的方法
def sum_num(n):
# 獲取當前執行該任務的線程對象
th = threading.currentThread()
print("當前正在執行的線程名字爲", th.name)
sum_re = 0
for i in range(n+1):
sum_re += n
print("求和結果:",sum_re)
# 創建子線程
# thread1 = Thread(target[綁定方法], args[參數列表(元組形式)],name[線程名稱])
thread1 = Thread(target=sum_num, args=(1000000000,), name="thread1")
# 啓動指定線程
thread1.start()
# setDaemon(True):如果設置爲True,則是將該線程設置爲主線程守護線程, 當主線程執行結束的時候,該線程不管任務是否完成都會被系統強制終止。thread1.setDaemon(True)的作用跟join的作用完全相反
thread1.setDaemon(True)
# join(timeout=time):設置主線程必須等待對應子線程的執行,等待指定的時間time之後,沒如果子線程沒有結果返回,主線程不再等待,繼續執行。如果該時刻之內返回,主線程立刻繼續執行,如果timeout設置爲None(默認),此時主線程必須等待子線程運行結束才能執行
thread1.join(timeout=1)
3.鎖(Lock)
當多個線程出現修改同一個公共資源的時候,爲了防止多個線程同時爭搶統一資源此時需要爲給一個線程對應的操作執行上鎖解鎖任務
lock
對象中內置兩個方法,分別是acquire
和release
,其中acquire()是獲取鎖的過程(上鎖),release()
是釋放鎖的過程(解鎖),但是注意如果程序運行過程中某一個線程在調用acquire()
上鎖之後沒有調用release()
解鎖,此時就會出現死鎖現象,此時程序會進入阻塞狀態
from threading import Thread
import threading
# 創建lock對象
lock = threading.Lock()
# 定義全局變量
count = 0
# 定義函數,完成對公共資源修改操作
def change(n):
# 獲取鎖,併爲當前線程上鎖
lock.acquire()
global count
count += n
count -= n
# 爲當前線程釋放鎖
lock.release()
# 定義函數完成對count進行n次修改
def target1(n):
for i in range(n):
change(i)
# 定義一個函數完成對count進行m次修改
def target2(m):
for i in range(m):
change(m)
# 創建兩個子線程,分別完成target1任務和target2任務
thread1 = Thread(target=target1, args=(100,), name='thread1')
thread2 = Thread(target=target2, args=(100,), name='thread2')
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(count)
如果不使用Lock對象對臨界資源進行上鎖解鎖,count值就會發生異常,隨機會得到不同的數字。
注意:上鎖解鎖最好使用在臨界區附近
4.遞歸鎖(RLock)
先來測試關於Lock的一個小例子:
import threading
# 創建lock對象
lock = threading.Lock()
print("程序準備要鎖")
lock.acquire()
print("程序上鎖")
lock.acquire()
print("程序再次上鎖")
lock.release()
lock.release()
print("程序結束")
Lock就像一個任性的大爺,當同一個線程申請兩次acquire操作且中間沒有執行release操作釋放資源時,Lock大爺就會掀桌子,程序會陷入死鎖狀態,永遠不會結束。
因此開發中爲了避免該種情況,建議使用RLock(遞歸鎖)
# 遞歸鎖(相對於Lock更加高級的鎖)
lock = threading.RLock()
print("程序準備要鎖")
lock.acquire()
print("程序上鎖")
lock.acquire()
print("程序再次上鎖")
lock.release()
lock.release()
print("程序結束")
同樣的程序,RLock就很人性化,當第一次申請上鎖之後,再次檢測到上鎖操作時,RLock發現當前並沒有可釋放的資源並且發現請求者是同一個線程,此時RLock並不會像Lock一樣直接掀桌子陷入死鎖狀態,而是直接略過該請求,直接執行之後的語句。
5.基於高級鎖的線程處理機制(Condition)
Condition()
:threading內置的高級鎖對象,內部默認封裝的是一個RLock對象
Condition內部封裝的也是acquire和release操作,只不過該操作內部也是通過RLock間接操作上鎖和解鎖過程。
同時condition內部封裝了一個線程等待池,只要線程通過調用wait()
方法,此時線程會被自動丟到線程等待池中,直到另一個線程通過notify()
或者是notifyAll()
方法來喚醒等待池中的線程
Condition一般適用於兩個線程互相協作完成的任務
注意:notify(n)
默認每次只喚醒一個線程,但是當指定n的值時,此時可以同時喚醒n個線程
簡易捉迷藏例子:
import threading
import time
import random
con = threading.Condition()
# 該變量存儲隨機結果,判定誰輸誰贏
result = 0
# 定義一個負責找的孩子
def seeker(name):
con.acquire()
print("我%s已經蒙好眼睛了,你可以去藏了!" % name)
con.wait()
for i in range(3):
print("%s is seeking" % name)
time.sleep(2)
global result
result = random.randint(0, 1)
if result == 0:
print("找到你了,我%s贏了!" % name)
else:
print("出來吧,我%s輸了!" % name)
con.notify()
con.release()
# 定義另外一個孩子隱藏的過程
def hider(name):
con.acquire()
for i in range(3):
print("%s is hiding" % name)
time.sleep(2)
print("我%s已經藏好了,你來找我吧!" % name)
con.notify()
con.wait()
global result
if result == 0:
print("好可惜我%s輸了!" % name)
else:
print("哈哈太好了我%s贏了!" % name)
con.release()
# 創建線程1執行seeker操作
thread1 = threading.Thread(target=seeker, args=("熊大",), name="thread1")
# 創建線程2執行hider操作
thread2 = threading.Thread(target=hider, args=("熊二",), name='thread2')
thread1.start()
thread2.start()
6.基於時間的線程處理機制(Event)
Event
:事件處理機制,全局定義了一個Flag,如果Flag值爲False,那麼當線程執行event.wait()
,此時該線程進入阻塞狀態。但是如果Flag爲True,此時線程調用event.wait()
,線程不會處於阻塞狀態
clear()
:設置Flag爲False
set()
:設置Flag爲True
isset()
:判定當前Flag是否是True,默認狀態下Flag爲False
Event可以實現線程間的通信,使用Event可以使某一個線程處於等待狀態(阻塞狀態),等待其他線程通過set方法將Flag設置爲True,此時所有等待狀態的線程都會被喚醒
模擬紅綠黃燈例子:
import threading
from threading import Event
import time, random
# 創建Event對象
event = Event()
# 模擬起始位置爲紅燈
print("起始爲紅燈狀態")
# 創建紅綠燈
def light():
countR = 60
countG = 25
countY = 3
# 記錄當前燈的狀態(0:紅燈 1:綠燈 2:黃燈)
state = 0
while True:
if state == 0:
if event.is_set() == True:
event.clear()
time.sleep(1)
countR -= 1
if countR == 0:
print("紅燈轉成綠燈")
state = 1
countR = 10
elif state == 1:
if event.is_set() == False:
event.set()
time.sleep(1)
countG -= 1
if countG == 0:
print("綠燈轉成黃燈")
state = 2
countG = 5
else:
time.sleep(1)
countY -= 1
if countY == 0:
print("黃燈轉成紅燈")
state = 0
countY = 2
# 定義函數完成車輛運行
def car_run():
while True:
# 模擬一輛車通過時間
time.sleep(random.randint(1, 3))
if event.is_set() == False:
event.wait()
else:
print("當前有一輛汽車通過")
# 定義兩個線程分別完成紅綠燈的控制和汽車的控制
thread1 = threading.Thread(target=light, name="thread1")
thread2 = threading.Thread(target=car_run, name="thread2")
thread1.start()
thread2.start()
7.基於計數器的線程處理機制(Semaphore和BoundedSemaphore)
(1)Semaphore
threading.Semaphore(n)
限制同一時間最多n個子線程同時運行
Semaphore
運行過程中,通過計數器完成線程的操作,Semaphore每一次調用acquire
,此時計數器-1;每一次調用release
,此時計數器+1。必須保證計數器的值比0大此時其他線程才能正常運行,否則線程其他處於阻塞狀態。
模擬多個下載任務例子:
sem = threading.Semaphore(3)
def download():
sem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
sem.release()
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
(2)BoundedSemaphore
Semaphore
在使用過程中可以調用任意次release()
解鎖操作,在計數器中就相當於可以多次進行+1操作,這便會導致系統中可處理的線程數越來越多,使鎖機制名存實亡。
BoundedSemaphore
與Semaphore
類似,但BoundedSemaphore
會檢查計數器內部的值,保證值不會大於初始設定的值n,如果超出n,就回引發ValueError錯誤。
同樣是模擬多個下載任務例子:
bsem = threading.BoundedSemaphore(3)
def download():
bsem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
bsem.release()
# 注意這裏的兩個release操作會引發ValueError操作 正常操作請刪除或註釋一個
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
8.threading中獲取線程數據
(1)返回當前存活的線程對象的數量 threading.activeCount()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意這裏的兩個release操作會引發ValueError操作 正常操作請刪除或註釋一個
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 獲取當前存活線程數量
print(threading.activeCount())
# 43
# 主線程+42個子線程
(2)返回當前線程對象 threading.currentThread()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意這裏的兩個release操作會引發ValueError操作 正常操作請刪除或註釋一個
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 獲取當前線程對象
print(threading.currentThread())
# <_MainThread(MainThread, started 5216)>
# 主線程
(3)返回當前存在的所有線程對象的列表 threading.enumerate()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意這裏的兩個release操作會引發ValueError操作 正常操作請刪除或註釋一個
# ValueError: Semaphore released too many times
# 獲取列表太長所以我減到了2個
for i in range(2):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 獲取當前存活線程列表
print(threading.enumerate())
# [<_MainThread(MainThread, started 3788)>, <Thread(thread1, started 808)>, <Thread(thread2, started 3516)>]
(4)返回線程pid threading.get_ident()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意這裏的兩個release操作會引發ValueError操作 正常操作請刪除或註釋一個
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 獲取線程pid
print(threading.get_ident())
# 1376
(5)返回主線程對象 threading.main_thread()
bsem = threading.BoundedSemaphore(1)
def download():
bsem.acquire()
print("當前正在執行下載任務的線程是%s" % threading.currentThread().name)
time.sleep(random.randint(1, 3))
bsem.release()
# bsem.release()
# 注意這裏的兩個release操作會引發ValueError操作 正常操作請刪除或註釋一個
# ValueError: Semaphore released too many times
for i in range(42):
th = threading.Thread(target=download, name="thread{0}".format(i+1))
th.start()
# 獲取主線程對象
print(threading.main_thread())