程序池與執行緒池 協程相關知識
阿新 • • 發佈:2022-04-22
概要
-
GIL與普通互斥鎖區別
-
驗證多執行緒是否有用
-
死鎖現象(強調鎖不能輕易使用)
-
訊號量與event事件(瞭解)
-
程序池與執行緒池
-
協程內容
內容
1、GIL與普通互斥鎖的區別
# 1.先驗證GIL的存在 from threading import Thread, Lock import time money = 100 def task(): global money money -= 1 for i in range(100): # 相當於建立了一百個執行緒 t = Thread(target=task) t.start() print(money) # 0 沒有加鎖相當於100個執行緒同時運行了 # 2.在驗證不同資料加上不同的鎖 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) # 99 """ GIL是一個純理論知識 在實際工作中根本無需考慮它的存在 GIL作用面很窄 僅限於直譯器級別 可以認為是直譯器本身自帶的一種鎖 所以只能在直譯器上使用 後期我們要想保證資料的安全應該自定義互斥鎖(使用別人封裝好的工具)"""
2、驗證多執行緒作用
""" 兩個大前提 1.CPU的個數 單個 多個 2.任務的型別 IO密集型 計算密集型 """ # 單個CPU 多個IO密集型任務 多程序:浪費資源 無法利用多個CPU 多執行緒:節省資源 切換+儲存狀態 多個計算密集型任務 多程序:耗時更長 建立程序的消耗+切換消耗 多執行緒:耗時較短 切換消耗 # 多個CPU 多個IO密集型任務 多程序:浪費資源 多個cpu無用武之地 多執行緒:節省資源 切換+儲存狀態 多個計算密集型任務 多程序:利用多核 速度更快 多執行緒:速度較慢 結論:多程序和多執行緒都有具體的應用場景 尤其是多執行緒並不是沒有用!!! """程式碼驗證""" from threading import Thread from multiprocessing import Process import os import time def work(): res = 1 for i in range(1,1000): res *= i if __name__ == '__main__': # print(os.cpu_count()) # 4 檢視當前計算機cpu的個數 程序: start_time = time.time() p_list = [] for i in range(4): p = Process(target=work) p.start() p_list.append(p) for p in p_list: p.join() print('總耗時:%s' %(time.time()-start_time)) 執行緒: t_lsit = [] for i in range(4): t = Thread(target=work) t.start() t_list.append(t) for t in t_list: t.join() print('總耗時:%s' %(time.time()-start_time)) """ 以上總結: 計算密集型 多程序:0.08273792266845703 多執行緒:0.28725099563598633 兩者差了一個數量級(越多差距越大) 結論:多程序比多執行緒更好""" 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 兩者差了兩個數量級 結論 多執行緒更好"""
3、死鎖現象
# 鎖就算掌握瞭如何搶 如何放 也會產生死鎖現象 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() """鎖不能輕易使用並且以後我們也不會在自己去處理鎖都是用別人封裝的工具"""
4、訊號量
訊號量在不同的知識體系中 展示出來的功能是不一樣的
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()
# 只要是跟鎖相關的幾乎都不會讓我們自己去寫 後期還是用模組
5、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簡便)
6、程序池與執行緒池(重點)
補充:
服務端必備的三要素
1.24小時不間斷提供服務
2.固定的ip地址和埠port
3.支援高併發
回顧:
TCP服務端實現併發
多程序:來一個客戶端就開一個程序(臨時工)
多執行緒:來一個客戶端就開一個執行緒(臨時工)
問題:
計算機硬體是有物理極限 我們不可能無限制的建立程序和執行緒
措施:
池:保證計算機硬體安全的情況下提升程式的執行效率
程序池:提前建立好固定數量的程序 後續反覆使用這些程序(合同工)
執行緒池:提前建立好固定數量的執行緒 後續反覆使用這些執行緒(合同工)
如果任務超出了池子裡面的最大程序或執行緒數 則原地等待
強調:
程序池和執行緒池其實降低了程式的執行效率 但是保證了硬體的安全!!
# 程式碼演示(掌握)
from concurrent.futures import
ProcessPoolExecutor,ThreadPoolExecutor # 必寫程式碼
# 執行緒池
ProcessPoolExecutor(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)
7、協程
"""
程序:資源單位
執行緒:執行單位
協程:單執行緒下實現併發
併發的概念:切換+儲存狀態
首先需要強調的是協程完全是程式設計師自己意淫出來的名詞!!!
對於作業系統而言之認識程序和執行緒
協程就是自己通過程式碼來檢測程式的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 程式碼控制切換
8、基於協程實現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密集型的
所以協程我們知道它的存在即可 幾乎不會真正去自己編寫
"""