多執行緒,協程
阿新 • • 發佈:2022-04-21
目錄
GIL與普通互斥鎖的區別
# 先驗證GIL的存在 from threading import Thread,Lock import time money = 100 for i in range(100): # 建立一百個執行緒 t = Thread(target=task) t.start() print(money) # 在驗證不同資料加不同鎖 from threading import Thread, Lock import time money = 100 mutex = Lock() def task(): global money mutex.acquire() tmp = money time.sleep(0.1) money = tmp - 1 mutex.release() ''' 搶鎖的簡便寫法(with上下文管理) with mutex: pass ''' t_list = [] for i in range(100): # 建立一百個執行緒 t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() # 為確保結構正確 應該等待所有執行緒執行完畢在列印money print(money) ''' GIL是一個純理論知識 在實際工作中無需考慮它的純在 GIL作用面很窄,僅限於直譯器級別 後期我們想要保證資料的安全應該自定義互斥鎖(使用別人封裝好的工具) '''
驗證多執行緒的作用
''' 兩大前提 1.CPU個數 單個 多個 2.任務的型別 IO密集型 計算密集型 ''' # 單個CPU 多個IO密集型任務 多程序:浪費資源 無法利用多個CPU 多執行緒:節省資源 切換+儲存狀態 多個計算密集型任務 多程序:耗時更長 建立程序的消耗+切換消耗 多執行緒:耗時較短 切換消耗 # 多個CPU 多個IO密集型任務 多程序:浪費資源 多個CPU無用武之地 多執行緒:節省資源 切換+儲存狀態 多個計算密集型任務 多程序:利用多核 速度更快 多執行緒:速度較慢 結論:多程序和多執行緒都有具體的應用場景 尤其是多執行緒並不是沒有用!!! # 程式碼驗證 from multiprocessing import Process from threading import Thread import time def index(): res = 1 for i in range(1, 50000): res = i * res if __name__ == "__main__": start = time.time() p_list = [] for i in range(12): p = Process(target=index) p.start() p_list.append(p) for i in p_list: i.join() end = time.time() print(end - start) # 5.524967670440674 # start_time = time.time() # t_list = [] # for i in range(12): # t = Thread(target=index) # t.start() # t_list.append(t) # for i in t_list: # i.join() # end_time = time.time() # print(end_time - start_time) # 12.41026759147644 def work(): time.sleep(2) # 模擬純IO操作 if __name__ == '__main__': start_time = time.time() # t_list = [] # for i in range(100): # t = Thread(target=work) # t.start() # for t in t_list: # t.join() p_list = [] for i in range(100): p = Process(target=work) p.start() for p in p_list: p.join() print('總耗時:%s' % (time.time() - start_time)) """ IO密集型 多執行緒 總耗時:0.007348060607910156 多程序 總耗時:0.1564030647277832 兩者差了兩個數量級 結論 多執行緒更好 """
死鎖現象
# 鎖就算掌握瞭如何搶 如何放 也會產生死鎖現象 from threading import Thread, Lock import time mutexA = Lock() mutexB = Lock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print(f'{self.name}搶到了A鎖') mutexB.acquire() print(f'{self.name}搶到了B鎖') mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print(f'{self.name}搶到了B鎖') time.sleep(2) mutexA.acquire() print(f'{self.name}搶到了A鎖') mutexA.release() mutexB.release() for i in range(20): t = MyThread() t.start() ''' 鎖是不能輕易使用並且以後我們也不會自己處理鎖,都是用別人封裝的工具 '''
訊號量
訊號量在不同的知識體系中 展示出來的功能是不一樣的
eg:
在併發程式設計中訊號量意思是多把互斥鎖
在django框架中訊號量意思是達到某個條件自動觸發特定功能
"""
如果將自定義互斥鎖比喻成是單個廁所(一個坑位)
那麼訊號量相當於是公共廁所(多個坑位)
"""
from threading import Thread, Semaphore
import time
import random
sp = Semaphore(5) # 建立一個有五個坑位(帶門的)的公共廁所
def task(name):
sp.acquire() # 搶鎖
print('%s正在蹲坑' % name)
time.sleep(random.randint(1, 5))
sp.release() # 放鎖
for i in range(1, 31):
t = Thread(target=task, args=('傘兵%s號' % i, ))
t.start()
# 只要是跟鎖相關的幾乎都不會讓我們自己去寫 後期還是用模組
event事件
'''
子執行緒的執行可以有其他子執行緒決定
'''
from threading import Thread, Event
import time
event = Event() # 類似於造了一個紅綠燈
def light():
print('紅燈亮著的 所有人都不能動')
time.sleep(3)
print('綠燈亮了 油門踩到底 給我衝!!!')
event.set()
def car(name):
print('%s正在等紅燈' % name)
event.wait()
print('%s加油門 飆車了' % name)
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('熊貓PRO%s' % i,))
t.start()
# 這種效果其實也可以通過其他手段實現 比如佇列(只不過沒有event簡便)
程序池與執行緒池
'''
補充:
服務端必須具備三要素
1. 24小時不間斷提供服務
2. 固定的ip和port
3.支援高併發
回顧:
TCP服務端實現併發
多程序:進來一個客戶端就開一個程序
多執行緒:來一個客戶端就開一個執行緒
問題:
計算機硬體是有物理極限的 我們不可能無限制的建立程序和 執行緒
措施:
池:
保證電腦保安的情況下提升程式的執行效率
程序池:提前建立好固定數量的程序 後續會反覆使用這些程序
執行緒池:提前建立好固定數量的執行緒後續反覆使用這些執行緒
如果任務超出池子裡面的最大程序或執行緒數 則原地等待
強調:程序池和執行緒池其實降低了程式的執行效率 但是保證了硬體的安全
'''
# 程式碼演示
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
# 執行緒池
pool = ThreadPoolExecutor(5) # 執行緒池執行緒數預設是CPU個數的五倍 也可以自定義
'''上面的程式碼執行之後就會立刻建立五個等待工作的執行緒'''
'''不應該自己主動等待結果 應該讓非同步提交自動提醒>>>:非同步回撥機制'''
pool.submit(task, i).add_done_callback(func)
"""add_done_callback只要任務有結果了 就會自動呼叫括號內的函式處理"""
# 程序池
pool = ProcessPoolExecutor(5) # 程序池程序數預設是CPU個數 也可以自定義
'''上面的程式碼執行之後就會立刻建立五個等待工作的程序'''
pool.submit(task, i).add_done_callback(func)
協程
"""
程序:資源單位
執行緒:執行單位
協程:單執行緒下實現併發
併發的概念:切換+儲存狀態
首先需要強調的是協程完全是程式設計師自己意淫出來的名詞!!!
對於作業系統而言之認識程序和執行緒
協程就是自己通過程式碼來檢測程式的IO操作並自己處理 讓CPU感覺不到IO的存在從而最大幅度的佔用CPU
類似於一個人同時幹接待和服務客人的活 在接待與服務之間來回切換!!!
"""
# 基本使用
# 儲存的功能 我們其實接觸過 yield 但是無法做到檢測IO切換
from gevent import monkey;monkey.patch_all() # 固定編寫 用於檢測所有的IO操作
from gevent import spawn
import time
def play(name):
print('%s play 1' % name)
time.sleep(5)
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3)
print('%s eat 2' % name)
start_time = time.time()
g1 = spawn(play, 'jason')
g2 = spawn(eat, 'jason')
g1.join() # 等待檢測任務執行完畢
g2.join() # 等待檢測任務執行完畢
print('總耗時:', time.time() - start_time) # 正常序列肯定是8s+
# 5.00609827041626 程式碼控制切換
基於協程實現TCP服務端併發
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket
def communication(sock):
while True:
data = sock.recv(1024) # IO操作
print(data.decode('utf8'))
sock.send(data.upper())
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept() # IO操作
spawn(communication, sock)
g1 = spawn(get_server)
g1.join()
'''
終極結論
python可以通過開設多程序在多程序下開設多執行緒在多執行緒下使用協程 從而讓程式執行的效率達到極致
但是實際業務中很少需要如此之高的效率(一直佔著CPU不放)
因為大部分程式都是IO密集型的
所有協程知道存在就可,不需要真正自己取編寫
'''