文章目錄
- 執行緒的概念
- 創建多執行緒
- 主執行緒
- 阻塞執行緒
- 執行緒方法
- 執行緒同步
- 同步的概念
- Python中的鎖
- Python中的條件鎖
- 小結

我們常說的「手慢無」其實類似多執行緒同時競爭一個共享資源的結果,要保證結果的唯一正確性,而這讓我們從執行緒(Python)慢慢說起……
執行緒的概念
執行緒(Thread)是CPU分配資源的基本單位,一個程式開始運行就變成了一個行程,而一個行程相當于一個或多個執行緒,使用執行緒可以實作程式的并發,
一個程式中可以同時運行多個執行緒,用不同的執行緒完成不同的任務,
傳統的程式設計語言同一時刻只能執行單任務操作,效率很低,比如網路程式在接受資料時發生阻塞,而CPU資源處于閑置狀態,只能等到程式接受資料后才能繼續運行,
多執行緒實作后臺服務程式可以同時處理多個任務,并不發生阻塞現象,多執行緒程式設計最大的特點是能夠提高程式的執行效率和處理速度,Python程式可同時并行運行多個獨立執行緒,比如開發Email系統,創建一個執行緒用來接受資料,一個執行緒用來發送資料,即使發送執行緒在接受資料時被阻塞,接受資料執行緒仍然可以運行,互相獨立不影響,
Python的執行緒沒有優先級,也不能銷毀、停止和掛起、也沒有恢復、中斷,這和其他語言有所不同,
創建多執行緒
Python3.X實作多執行緒的是threading模塊,使用它可以創建多執行緒程式,并且在多執行緒間進行同步和通訊,
因為是一個模塊,所以使用前記得匯入:import threading
Python有如下兩種方式來創建執行緒,
一、通過threading.Thread()創建
Thread()語法如下:
threading.Thread(group=None,target=None,name=None,args=(),kwargs=(),*,deamon=None)
- group:必須為None,與ThreadGroup類相關,一般不使用,
- target:目標函式
- name:執行緒名,默認Thread-x(x從1開始)
- args:為目標函式傳遞實參、元組
- kwargs:為目標函式傳遞關鍵字引數、字典
- daemon:用來設定執行緒是否隨主執行緒退出而退出
import threading
def test(x,y):
for i in range(x,y):
print(i)
thread1 = threading.Thread(name='t1',target=test,args=(0,5))
thread2 = threading.Thread(name='t2',target=test,args=(5,10))
thread1.start()
thread2.start()

二、通過繼承threading.Thread類創建
thread.Thread是一個類,可以使用單繼承的方式創建一個自己的子類,
import threading
class mythread(threading.Thread):
def run(self): #重寫父類run方法
for i in range(0,5):
print(i)
thread1 = mythread()
thread2 = mythread()
thread1.start()
thread2.start()

如果呼叫時使用run而不是start,那么run()僅僅時被當作一個普通的函式使用,只有在執行緒為start時,它才是多執行緒的一種呼叫函式,
主執行緒
介紹主行程前,首先簡要介紹下父行程和子行程,如果執行緒A中啟動了一個執行緒B,那么A就是B的父行程,B就是A的子行程,
Python中,主執行緒是第一個啟動的執行緒,創建執行緒時有一個daemon屬性可以用來判斷主執行緒,當其值為False時,子行程不會雖主執行緒退出而退出,反之當其值為True時,如果主行程結束,則它的子進程也會被強制結束,
使用daemon屬性幾個注意事項:
- 每個行程都有
daemon屬性,可以不設定,默認值None - 從主執行緒創建的所有行程不設定
daemon屬性,默認都是False daemon屬性必須在start()之前設定,否則會引發RuntimeError- 若子執行緒不設定
daemon屬性,就取當前daemon來設定,子行程繼承子行程的daemon值,作用和設定None一樣 daemon=True測驗并不適用于IDLE環境中的互動模式或腳本運行模式,因為在該環節中的主執行緒只有在退出Pyhton IDLE時才終止,


阻塞執行緒
多執行緒提供了一個方法join()來阻塞執行緒,在一個執行緒中呼叫另一個執行緒的join()方法,呼叫者將被阻塞,直到被呼叫執行緒終止,
語法:join(timeout=None)

timeout傳參是設定超時值,當行程阻塞時間超過該值后,強制結束這個行程,

(
插播反爬資訊)博主CSDN地址:https://wzlodq.blog.csdn.net/
執行緒方法
前面提到的start、join等都是threading.Thread類的方法,
| 方法 | 說明 |
|---|---|
| run | 表示執行緒活動的方法 |
| start | 啟動執行緒 |
| join | 等待至執行緒終止 |
| is_alive | 回傳執行緒是否活動 |
| getName | 回傳執行緒名稱 |
| setName | 設定執行緒名稱 |
import time
import threading
def test():
time.sleep(3) #等待3秒
for i in range(0,5):
print(i)
thread1 = threading.Thread(target=test)
print('是否活動:',thread1.is_alive())
thread1.start()
print('是否活動:',thread1.is_alive())
print(thread1.getName())# 默認threac-x
thread1.setName('thread1')
print(thread1.getName())
thread1.join()
print('記得一鍵三連')

執行緒同步
同步的概念
Python應用程式中的多執行緒可以共享資源,如檔案、資料庫、記憶體等,當執行緒以并發形式訪問資料時,共享資料可能會產生沖突,Python引入執行緒同步的概念,以實作共享資料的一致性,執行緒同步機制讓多個執行緒有序的訪問共享資源,而不是同時操作共享資源,
可以通過購物秒殺的例子來進一步理解同步的概念,比如商品的庫存量是1,現在有兩個人在平臺上同時購買該商品,此時第一個執行緒查詢資料庫發現庫存量是1可以出售,正準備出售此商品;而同時第二個執行緒也查詢到該商品可以出售并且立即點擊購買,這時執行緒1執行購買時,出現出售兩次的錯誤,大于原庫存量1,這就是由于資料不同步導致的錯誤,(手慢無)
Python中的鎖
Python中的threading模塊提供了RLock鎖(可重入鎖)解決方案,一個時間只能讓一個執行緒操作陳述句放到Rlock的acquire()方法上鎖和release()方法解鎖,
import threading
class mythread(threading.Thread):
def run(self):
global value # 全域變數
lock.acquire() # 上鎖
value+=10 # 設定值
print('%s:%d'%(self.name,value)) # 讀取值
lock.release() # 解鎖
value = 0 # 初始化
lock=threading.RLock() # 創建可重入鎖
thread = [] # 存放執行緒
for i in range(3): # 創建3個執行緒
thread.append(mythread())
for i in thread: # 開啟執行緒
i.start()

上述代碼中,創建了3個執行緒,為了讀取value值時不產生錯誤,保證輸出值正確,使用了RLock鎖將設定值和讀取值鎖起來,以保證執行緒的同步,
Python中的條件鎖
Python的threading還提供了一個方法Conditing(),稱為Python中的條件變數,換句話說,這個條件變數必須與一個鎖關聯,所以也稱為條件鎖,用于比較復雜的同步,
比如一個執行緒上鎖后、解鎖前因為某一條件一直阻塞著,所以就一直解不開鎖,其他執行緒也會一直獲取不了鎖而導致被迫阻塞著,即所謂的死鎖,
這種情況下,變數鎖可以讓該執行緒先解鎖,然后阻塞著,等待條件滿足了再重新喚醒并上鎖,這樣就不會因為一個執行緒有問題而影響其他執行緒了,
條件鎖的原理跟設計模式的生產者/消費者模式類似,生產者是一段用于生產的內容,生產的成果供消費者消費,這中間設計一個快取池用來存盤資料,稱為倉庫,
- 生產者僅僅在倉庫未滿時生產,倉庫滿則停止生產,
- 消費者僅僅在倉庫有產品時才能消費,空倉則等待,
- 當消費者發現倉庫沒有產品時可通知生產者生產,
- 生產者生產可消費產品后,應該通知消費者去消費,
條件鎖常用方法:
| 方法 | 說明 |
|---|---|
| acquire | 呼叫關聯鎖相關方法 |
| release | 解鎖 |
| wait | 使執行緒進入等待池等待通知并解放鎖,使用前須獲得鎖定否則報錯 |
| notify | 從等待池挑選一個執行緒并通知,收到通知的執行緒將自動呼叫acquire()嘗試獲得鎖定(進入鎖定池);其他執行緒不會釋放鎖定,使用前須獲得鎖定否則報錯 |
| notifyAll | 通知等待池中所有執行緒,這些執行緒都將進入鎖定吃嘗試獲得鎖定,呼叫這個方法不會釋放鎖定,使用前須獲得鎖定否則報錯 |
以生產者/消費者為例:
import time
import threading
products = []
condition = threading.Condition()
class Consumer(threading.Thread): # 消費者
def consume(self): # 消費
global condition
global products
condition.acquire() # 上鎖
if len(products) == 0: # 判空
condition.wait() # 進入等待池等待通知
print('消費者:沒有產品了')
products.pop() # 消費一個產品
print('消費者:已消費一個產品,剩余可消費產品數為'+str(len(products)))
condition.notify() # 通知
condition.release() # 解鎖
def run(self):
for i in range(0,10):
time.sleep(3) # 設3秒消費一個產品
self.consume()
class Producer(threading.Thread): # 生產者
def produce(self):
global condition
global products
condition.acquire() # 設定條件鎖
if len(products) == 5: # 滿倉
condition.wait() # 進入等待池等待通知
print('生產者:已滿倉,停止生產')
products.append(1) # 生產一個產品
print('生產者:已生產一個產品,剩余可消費產品數為'+str(len(products)))
condition.notify() # 通知
condition.release() #解鎖
def run(self):
for i in range(0,10):
time.sleep(1) # 設1秒生產一個產品
self.produce()
producer = Producer()
consumer = Consumer()
producer.start()
consumer.start()
producer.join()
consumer.join()
上述代碼用time.sleep()來控制生產和消費的時間,當產品生產數量達到上限時就停止生產,并呼叫wait等待執行緒通知;當剩余可消費產品為0時也停止消費,等待執行緒通知,

小結
處理大批流程都類似的程式時,使用多執行緒可以有效節省時間,耗費的不過時一些計算機資源,是典型的以資源換時間,以目前計算機的性能來看,大多都是性能過剩的,理由剩余的計算機資源來節省時間非常合算,
使用多執行緒是要注意鎖的使用,使用鎖來保護共享的資源、資料,避免被其他的執行緒破壞,一般使用互斥鎖就可以應付大多數情況了,
Python系列博客持續更新中
原創不易,請勿轉載(
本不富裕的訪問量雪上加霜)
博主首頁:https://wzlodq.blog.csdn.net/
微信公眾號:唔仄lo咚鏘
如果文章對你有幫助,記得一鍵三連?
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/263830.html
標籤:python
