1. 程式人生 > >第三十八天 GIL 程序池與執行緒池

第三十八天 GIL 程序池與執行緒池

今日內容:

1.GIL 全域性直譯器鎖

2.Cpython直譯器併發效率驗證

3.執行緒互斥鎖和GIL對比

4.程序池與執行緒池

一.全域性直譯器鎖

  1.GIL:全域性直譯器鎖

    GIL本質就是一把互斥鎖,是夾在直譯器身上的

    統一程序內的所有執行緒都需要先搶到GIL鎖,才能執行pai直譯器程式碼

  2.GIL優缺點:

    優點:

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

    缺點:

      同一程序內所有的執行緒同一時刻只能有一個執行,

      也就是鎖Cpython直譯器多執行緒無法實現真正的並行

from
threading import Thread,current_thread import time def task(): print("%s is running"%current_thread().name) time.sleep(3) print("%s is done"current_thread().name) if __name__=="__main__": t1=Thread(target=task) t2=Thread(target=task) t3=Thread(target=task) t1.start() t2.start() t3.start()

二.Cpython直譯器併發效率驗證

關於GIL效能的討論

直譯器加鎖以後
將導致所有執行緒只能併發 不能達到真正的並行 意味著同一時間只有一個CPU在處理你的執行緒
給你的感覺是效率低

程式碼執行有兩種狀態
阻塞 i/o 失去CPU的執行權 (CPU等待IO完成)
非阻塞 程式碼正常執行 比如迴圈一千萬次 中途CPU可能切換 很快會回來 (CPU在計算)

假如有32核CPU 要處理一個下載任務 網路速度慢 100k/s 檔案大小為1024kb
如果你的程式碼中IO操作非常多 cpu效能不能直接決定你的任務處理速度


案例:
目前有三個任務 每個任務處理需一秒 獲取元資料需要一小時
3個CPU 需要 一小時1秒
1個cpu 需要 一小時3秒


在IO密集的程式中 CPU效能無法直接決定程式的執行速度 python就應該幹這種活兒
在計算密集的程式中 CPU效能可以直接決定程式的執行速度

#計算密集型測試
from
threading import Thread from multiprocessing import Process import time # 計算密集任務 def task1(): sum = 1 for i in range(10000000): sum *= i def task2(): sum = 1 for i in range(10000000): sum *= i def task3(): sum = 1 for i in range(10000000): sum *= i def task4(): sum = 1 for i in range(10000000): sum *= i def task5(): sum = 1 for i in range(10000000): sum *= i def task6(): sum = 1 for i in range(10000000): sum *= i if __name__ == '__main__': # 開始時間 st_time = time.time() # 多執行緒情況下 # t1 = Thread(target=task1) # t2 = Thread(target=task2) # t3 = Thread(target=task3) # t4 = Thread(target=task4) # t5 = Thread(target=task5) # t6 = Thread(target=task6) t1 = Process(target=task1) t2 = Process(target=task2) t3 = Process(target=task3) t4 = Process(target=task4) t5 = Process(target=task5) t6 = Process(target=task6) t1.start() t2.start() t3.start() t4.start() t5.start() t6.start() # # t1.join() # t2.join() # t3.join() # t4.join() # t5.join() # t6.join() print(time.time() - st_time)
from threading import Thread
from multiprocessing import Process
import time


# 計算密集任務
def task1():
    time.sleep(3)


def task2():
    time.sleep(3)


def task3():
    time.sleep(3)


def task4():
    time.sleep(3)


def task5():
    time.sleep(3)


def task6():
    time.sleep(3)

if __name__ == '__main__':

    # 開始時間
    st_time = time.time()
    # 多執行緒情況下
    # t1 = Thread(target=task1)
    # t2 = Thread(target=task2)
    # t3 = Thread(target=task3)
    # t4 = Thread(target=task4)
    # t5 = Thread(target=task5)
    # t6 = Thread(target=task6)


    t1 = Process(target=task1)
    t2 = Process(target=task2)
    t3 = Process(target=task3)
    t4 = Process(target=task4)
    t5 = Process(target=task5)
    t6 = Process(target=task6)

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()
    t6.start()

    # t1.join()
    # t2.join()
    # t3.join()
    # t4.join()
    # t5.join()
    # t6.join()

    print(time.time() - st_time)

三.GIL與互斥鎖

from  threading import Thread,Lock
import time

mutex = Lock()
num = 1
def task():
    global num
    # print(num)
    mutex.acquire()
    temp = num
    print(temp)
    time.sleep(1)   # 當你們執行緒中出現io時 GIL鎖就解開
    num = temp + 1
    mutex.release()  # 執行緒任務結束時GIL鎖解開


t1 = Thread(target=task,)

t2 = Thread(target=task,)
t1.start()
t2.start()
t1.join()
t2.join()
print(num)

GIL 和自定義互斥鎖的區別

全域性鎖不能保證自己開啟的執行緒安全 但要保證直譯器中的資料的安全

GIL 線上程呼叫直譯器是 自動加鎖 在IO阻塞時或執行緒執行完畢時 自動解鎖

四.程序池和執行緒池

  程序池

    就是一個裝程序的容器

  為什麼出現

    當程序很多的時候方便管理程序

  什麼時候用?

    當併發量特別大的時候 列入雙十一

    很多時候程序是空閒的 就讓他進入程序池 讓有任務處理時才從程序取出來使用

  程序池使用

    ProcessPoolExecutor類

    建立時指定最大程序數 自動建立程序

    呼叫submit函式將任務提交程序池中

    建立程序是在呼叫submit後發生

  總結一下:

    程序池可以自動創造程序

    程序現在最大程序數

    自動選擇一個空閒的程序幫你處理任務

  程序什麼時候算是空閒?

    程式碼執行完算是空閒

  IO密集時 用執行緒池

  計算密集時 用執行緒池

 

 

 

 

作業:

1、整理GIL直譯器鎖,解釋以下問題

1、什麼是GIL

GIL = Global Interpreter Lock (全域性解釋鎖器鎖)
2、有了GIL會對單程序下的多個執行緒造成什麼樣的影響

防止對個執行緒競爭統一資源造成資料的錯亂,只能有一個執行緒進行讀寫操作.
3、為什麼要有GIL

為了避免資源競爭造成的資料錯亂
4、GIL與自定義互斥鎖的區別,多個執行緒爭搶GIL與自定義互斥鎖的過程分析

GIL不保證自己開啟執行緒的安全 但保證直譯器中資料的安全

GIL 線上程呼叫解釋其時 自動加鎖 在IO阻塞是或執行緒程式碼執行完畢時,自動解鎖
5、什麼時候用python的多執行緒,什麼時候用多程序,為什麼?

2、程序池與執行緒池
1、池的用途,為何要用它

池用來儲存執行緒和程序,可以方便程序和執行緒的管理
2、池子裡什麼時候裝程序什麼時候裝執行緒?

  計算密集時裝程序,IO密集時裝執行緒

3、基於程序池與執行緒池實現併發的套接字通訊

#服務端

from socket import *
from threading import Thread
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor

tpool=ThreadPoolExecutor(3)

def communicte(conn,client_addr):
    while True:
        try:
            data= conn.recv(1024)
            if not data:break
            conn.send(data.upper())
        except ConnectionAbortedError:
            break
    conn.close()

def server():
    server = socket(AF_INET,SOCK_STREAM)
    server.bind(('127.0.0.1',8080))
    server.listen(5)

    while True:
        conn,client_addr=server.accept()
        print(client_addr)
        tpool.submit(communicte,conn,client_addr)
    server.close()

if __name__=='__main__':
    server()
#客戶端
import socket

c = socket.socket()
c.connect(('127.0.0.1',8080))
while True:
    msg = input(">>>:")
    c.send(msg.encode("utf-8"))
    data = c.recv(1024)
    print(data.decode("utf-8"))

 

4、基於執行緒池實現一個可以支援併發通訊的套接字,完成以下功能?
執行客戶端程式,使用者可選的功能有:
1、登入
2、註冊
3、上傳
4、下載

思路解析:
1、執行登入,輸入使用者名稱egon,密碼123,對使用者名稱egon和密碼進行hash校驗,並加鹽處理,將密文密碼傳送到服務端,與服務端事先存好使用者名稱與密文密碼進行對比,對比成功後,
在服務端記憶體中用hash演算法生成一個隨機字串比如eadc05b6c5dda1f8772c4f4ca64db110
然後將該字串傳送給使用者以及登入成功的提示資訊傳送給客戶端,然後在服務存放好
current_users={
'a3sc05b6c5dda1f8313c4f4ca64db110':{'uid':0,'username':'alex'},
'e31adfc05b6c5dda1f8772c4f4ca64b0':{'uid':1,'username':'lxx'},
'eadc05b6c5dda1f8772c4f4ca64db110':{'uid':2,'username':'egon'},

}

使用者在收到服務端發來的'eadc05b6c5dda1f8772c4f4ca64db110'以及登入成功的提示資訊後,以後的任何操作都會攜帶該隨機字串'eadc05b6c5dda1f8772c4f4ca64db110‘,服務端會根據該字串獲取使用者資訊來進行與該使用者匹配的操作

在使用者關閉連線後,服務端會從current_users字典中清除使用者資訊,下次重新登入,會產生新的隨機字串
這樣做的好處:
1、使用者的敏感資訊全都存放到服務端,更加安全

2、每次登入都拿到一個新的隨機的字串,不容易被偽

2、執行註冊功能,提交到服務端,然後存放到檔案中,如果使用者已經存在則提示使用者已經註冊過,要求重新輸入使用者資訊

3、執行上次下載功能時會攜帶使用者的隨機字串到服務端,如果服務端發現該字串not in current_users,則要求使用者先登入