1. 程式人生 > >python GIL鎖、程序池與執行緒池、同步非同步

python GIL鎖、程序池與執行緒池、同步非同步

一、GIL全域性直譯器鎖

  • 全域性直譯器鎖

    在CPython中,全域性直譯器鎖(GIL)是一個互斥鎖,它可以防止多個本機執行緒同時執行Python程式碼。之所以需要這個鎖,主要是因為CPython的記憶體管理不是執行緒安全的。(然而,自從GIL存在以來,其他特性已經逐漸依賴於它所執行的保證)

    • 什麼是GIL

      全域性直譯器鎖, 施加在直譯器上的互斥鎖

    • 為什麼需要GIL

      由於CPython的記憶體管理時非執行緒安全,於是CPython就給直譯器加上鎖, 解決了安全問題.

  • GIL的加鎖與解鎖時機

    • 加鎖的時機: 在呼叫直譯器時立即加鎖

    • 解鎖的時機:

      • 當前執行緒遇到了IO時

      • 當前執行緒執行時間超過設定值時 , 一旦達到某個閾值 , CPU會通知執行緒儲存狀態切換執行緒 , 以此來保證資料安全

  • 總結 : 由於GIL鎖的特性 , 我們需要考慮什麼情況下用多執行緒什麼情況下用多程序

    • 在單核情況下 , 無論是IO密集型還是計算密集型 , GIL都不會產生影響,多執行緒會更加經濟實惠.

    • 在多核情況下 , IO密集型會受到GIL的影響

      • 對於計算密集型 , 需要並行處理 , 所以需要用到多程序

      • 對於IO密集型 , 由於IO時間較長 , 建立程序不經濟 , 所以應該用多執行緒

二、GIL帶來的問題

  • GIL的優缺點:

    • 優點:保證Cpython直譯器記憶體管理的執行緒安全

    • 缺點: ​ 同一程序內所有的執行緒同一時刻只能有一個執行, ​ 也就說Cpython直譯器的多執行緒無法實現並行

三、執行緒池和程序池

  • 什麼是程序/執行緒池

    池表示一個容器 , 那麼程序或執行緒池表示的就是一個存放程序或執行緒的列表

  • 什麼時候使用執行緒池/程序池

    如果是IO密集型任務使用執行緒池 , 如果是計算密集型任務則使用程序池

  • 多執行緒Tcp通訊

  服務端

from
concurrent.futures import ThreadPoolExecutor from threading import Thread import socket ​ server = socket.socket() server.bind(("127.0.0.1",8806)) server.listen() ​ # 建立執行緒池 指定最大執行緒數為3 如果不指定 預設為CPU核心數 * 5 pool = ThreadPoolExecutor(3) # 不會立即開啟子執行緒 def task(client): while True: try: data = client.recv(1024) if not data: client.close() break client.send(data.upper()) except Exception: client.close() break while True: client, addr = server.accept() # submit:提交任務到執行緒池,第一次提交任務時會建立程序 ,後續再提交任務,直接交給以及存在的程序來完成,如果沒有空閒程序就等待 pool.submit(task, client)
View Code

 

  客戶端
from threading import Thread
import socket
​
c = socket.socket()
c.connect(("127.0.0.1",8806))
​
def send_msg():
    while True:
        msg = input(">>>:")
        if not msg:
            continue
        c.send(msg.encode("utf-8"))
​
send_t = Thread(target=send_msg)
send_t.start()
​
while True:
    try:
        data = c.recv(1024)
        print(data.decode("utf-8"))
    except:
        c.close()
        break
View Code
 

四、自定義的執行緒鎖與GIL的區別

GIL保護的是直譯器級別的資料安全,比如物件的引用計數,垃圾分代資料等等,具體參考垃圾回收機制詳解。

自定義執行緒鎖保證的是程序內的資源在同一時間只能有一個執行緒去訪問,保護的是程序內資源資料安全

四、同步與非同步

阻塞與非阻塞:

阻塞 : 程式遇到IO操作,無法繼續執行程式碼 , 叫做阻塞

非阻塞 : 沒有阻塞 , 正常執行

同步與非同步

提交任務的兩種方式

  • 同步:

    發起任務後必須等待任務結束,拿到一個結果才能繼續執行

  • 非同步 : 提交任務後,不再原地等待,直接執行下一行程式碼,(執行結果?)

    非同步效率高於同步

使用程序池 , 來實現非同步任務

# 非同步提交
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor()  # 不設定執行緒池最大數量預設為CPU核心數*5
​
​
def task(i):
​
    time.sleep(1)
    print("sub thread run..")
    i += 100
    return i
​
fs = []
for i in range(10):
    f = pool.submit(task,i) # submit就是一非同步的方式提交任務,會返回一個物件,通過呼叫result方法獲得結果
    fs.append(f)  # 將submit物件新增到列表中
​
​
​
# 是一個阻塞函式,會等到池子中所有任務完成後繼續執行
pool.shutdown(wait=True)  # 任務執行結束拿到結果後才會執行下一步
# pool.submit(task,1) # 注意 在shutdown之後 就不能提交新任務了
for i in fs:
    print(i.result())  # 迴圈取執行結果
print("over")
​
​
# 將程式改為同步提交
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor()  # 不設定執行緒池最大數量預設為CPU核心數*5
​
​
def task(i):
​
    time.sleep(1)
    print("sub thread run..")
    i += 100
    return i
​
for i in range(10):
    f = pool.submit(task,i) # submit就是一非同步的方式提交任務,會返回一個物件,通過呼叫result方法獲得結果
    res = f.result()  # result是一個阻塞函式,不拿到結果不執行下一步
print(res) 
​
print("over")
View Code

同步提交任務 , 程式卡住 , 不一定是阻塞,因為任務中可能在做一堆計算任務 , CPU沒走開。