递归锁、信号量、GIL锁、基于多线程的socket通信和进程池线程池
遞歸鎖、信號量、GIL鎖、基于多線程的socket通信和進程池線程池
遞歸鎖
死鎖現象:是指兩個或兩個以上的進程和線程因搶奪計算機資源而產生的一種互相等待的現象
from threading import Thread from threading import Lock import time lock_A = Lock() lock_B = Lock() class MyThread(Thread):def run(self):self.f1()self.f2()def f1(self):lock_A.acquire()print(f"{self.name}拿到了A鎖")lock_B.acquire()print(f"{self.name}拿到了B鎖")lock_B.release()lock_A.release()def f2(self):lock_B.acquire()print(f"{self.name}拿到了B鎖")time.sleep(0.1)lock_A.acquire()print(f"{self.name}拿到了A鎖")lock_A.release()lock_B.release() if __name__ == '__main__':for i in range(3):t = MyThread()t.start() # 結果: Thread-1拿到了A鎖 Thread-1拿到了B鎖 Thread-1拿到了B鎖 Thread-2拿到了A鎖遞歸鎖:
遞歸鎖有一個計數的功能, 原數字為0,上一次鎖,計數+1,釋放一次鎖,計數-1,
只要遞歸鎖上面的數字不為零,其他線程就不能搶鎖.
from threading import Thread from threading import RLock import time lock_A = lock_B = RLock() class MyThread(Thread):def run(self):self.f1()self.f2()def f1(self):lock_A.acquire()print(f"{self.name}拿到了A鎖")lock_B.acquire()print(f"{self.name}拿到了B鎖")lock_B.release()lock_A.release()def f2(self):lock_B.acquire()print(f"{self.name}拿到了B鎖")time.sleep(0.1)lock_A.acquire()print(f"{self.name}拿到了A鎖")lock_A.release()lock_B.release() if __name__ == '__main__':for i in range(3):t = MyThread()t.start()信號量
也是一種鎖, 控制并發數量
from threading import Thread,Semaphore,current_thread import time import random sem = Semaphore(5) def task():sem.acquire()print(f"{current_thread().name}廁所ing")time.sleep(random.randint(1,3))print(f"{current_thread().name}廁所ed")sem.release() if __name__ == '__main__':for i in range(20):t = Thread(target=task,)t.start()GIL鎖
GIL鎖的定義:
全局解釋鎖,就是一把互斥鎖,將并發變成串行,同一時刻只能有一個線程使用解釋器資源,犧牲效率,保證解釋器的數據安全。
py文件在內存中的執行過程:
- 當執行py文件時,會在內存中開啟一個進程
- 進程中不光包括py文件還有python解釋器,py文件中的線程會將代碼交給解釋器,
- 解釋器將python代碼轉化為C語言能識別的字節碼,然后再交給解釋器中的虛擬機將字節碼轉化為二進制碼最后交給CPU執行
當線程1先拿到GIL鎖時線程2、線程3就只能等待,當線程1在CPU執行遇到阻塞或執行一段時間后,線程1會被掛起,同時GIL鎖會被釋放,此時線程2或線程3就會拿到鎖進入解釋器,同樣,當在CPU執行遇到阻塞或執行一段時間后被掛起,同時GIL鎖會被釋放,此時最后一個線程就會進入解釋器。
從上面可以看出,當遇到單個進程中含有多個線程時,由于GIL鎖的存在,Cpython并不能利用多核進行并行處理,但可以在單核實現并發。
但不同進程之間的多線程是可以利用多核的。
GIL鎖的兩個作用:
1、保證解釋器里面的數據的安全;
2、強行加鎖,減輕開發負擔
問題:單進程的多線程不能利用多核
如何判斷什么情況使用多線程并發與多進程并發
對計算來說,cpu越多越好,但是對于I/O來說,再多的cpu也沒用
當然對運行一個程序來說,隨著cpu的增多執行效率肯定會有所提高(不管提高幅度多大,總會有所提高),這是因為一個程序基本上不會是純計算或者純I/O,所以應該相對的去看一個程序到底是計算密集型還是I/O密集型,如下:
#分析: 我們有四個任務需要處理,處理方式肯定是要達到并發的效果,解決方案可以是: 方案一:開啟四個進程 方案二:一個進程下,開啟四個線程#單核情況下,分析結果: 如果四個任務是計算密集型,沒有多核來并行計算,方案一徒增了創建進程的開銷,方案二勝如果四個任務是I/O密集型,方案一創建進程的開銷大,且進程的切換速度遠不如線程,方案二勝#多核情況下,分析結果:如果四個任務是計算密集型,多核意味著并行計算,在python中一個進程中同一時刻只有一個線程執行,可以利用多核,方案一勝如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝#結論:現在的計算機基本上都是多核,python對于計算密集型的任務開多線程的效率并不能帶來多大性能上的提升,甚至不如串行(沒有大量切換),但是,對于IO密集型的任務效率還是有顯著提升的。總結:多核前提下,如果任務IO密集型,使用多線程并發;如果任務計算密集型,使用多進程并發。
驗證Cpython的并發效率
- 計算密集型: 單個進程的多線程并發 vs 多個進程的并發并行
? 總結: 計算密集型: 多進程的并發并行效率高.
- IO密集型: 單個進程的多線程并發 vs 多個進程的并發并行
? 總結:對于IO密集型: 單個進程的多線程的并發效率高.
基于多線程的socket通信
客戶端:
import socketclient = socket.socket()client.connect(('127.0.0.1',8848))while 1:try:to_server_data = input('>>>').strip()client.send(to_server_data.encode('utf-8'))from_server_data = client.recv(1024)print(f'來自服務端的消息: {from_server_data.decode("utf-8")}')except Exception:break client.close()服務端:
import socket from threading import Threaddef communicate(conn,addr):while 1:try:from_client_data = conn.recv(1024)print(f'來自客戶端{addr[1]}的消息: {from_client_data.decode("utf-8")}')to_client_data = input('>>>').strip()conn.send(to_client_data.encode('utf-8'))except Exception:breakconn.close()def _accept():server = socket.socket()server.bind(('127.0.0.1', 8848))server.listen(5)while 1:conn, addr = server.accept()t = Thread(target=communicate,args=(conn,addr))t.start()if __name__ == '__main__':_accept()進程池線程池
線程池: 一個容器,這個容器限制住你開啟線程的數量,比如4個,第一次肯定只能并發的處理4個任務,只要有任務完成,線程馬上就會接下一個任務.
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import os import time import random# print(os.cpu_count()) 獲取cpu數量 def task(n):print(f'{os.getpid()} 接客')time.sleep(random.randint(1,3))# 開啟進程池 (并行(并行+并發)) if __name__ == '__main__':p = ProcessPoolExecutor() # 進程池,默認不寫,開啟數量為cpu數量for i in range(20):p.submit(task,i)# 開啟線程池 (并發)t = ThreadPoolExecutor() # 默認不寫, cpu個數*5 線程數# t = ThreadPoolExecutor(100) # 100個線程for i in range(20):t.submit(task,i)轉載于:https://www.cnblogs.com/lifangzheng/p/11415009.html
總結
以上是生活随笔為你收集整理的递归锁、信号量、GIL锁、基于多线程的socket通信和进程池线程池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《仙剑奇侠传7》在海外受好评 开发商发文
- 下一篇: 阻塞、非阻塞、同步与异步