日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

Python学习:线程池原理及实现

發布時間:2023/12/20 python 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python学习:线程池原理及实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

傳統多線程方案會使用“即時創建, 即時銷毀”的策略。盡管與創建進程相比,創建線程的時間已經大大的縮短,但是如果提交給線程的任務是執行時間較短,而且執行次數極其頻繁,那么服務器將處于不停的創建線程,銷毀線程的狀態。

一個線程的運行時間可以分為3部分:線程的啟動時間、線程體的運行時間和線程的銷毀時間。在多線程處理的情景中,如果線程不能被重用,就意味著每次創建都需要經過啟動、銷毀和運行3個過程。這必然會增加系統相應的時間,降低了效率。

1.使用線程池:

由于線程預先被創建并放入線程池中,同時處理完當前任務之后并不銷毀而是被安排處理下一個任務,因此能夠避免多次創建線程,從而節省線程創建和銷毀的開銷,能帶來更好的性能和系統穩定性。

2.線程池模型

這里使用創建Thread()實例來實現,下面會再用繼承threading.Thread()的類來實現

# 創建隊列實例, 用于存儲任務 queue = Queue()# 定義需要線程池執行的任務 def do_job():while True:i = queue.get()time.sleep(1)print 'index %s, curent: %s' % (i, threading.current_thread())queue.task_done()if __name__ == '__main__':# 創建包括3個線程的線程池for i in range(3):t = Thread(target=do_job)t.daemon=True # 設置線程daemon 主線程退出,daemon線程也會推出,即時正在運行t.start()# 模擬創建線程池3秒后塞進10個任務到隊列time.sleep(3)for i in range(10):queue.put(i)queue.join()

daemon說明:

如果某個子線程的daemon屬性為False,主線程結束時會檢測該子線程是否結束,如果該子線程還在運行,則主線程會等待它完成后再退出;

如果某個子線程的daemon屬性為True,主線程運行結束時不對這個子線程進行檢查而直接退出,同時所有daemon值為True的子線程將隨主線程一起結束,而不論是否運行完成。

daemon=True 說明線程是守護線程,守護線程外部沒法觸發它的退出,所以主線程退出就直接讓子線程跟隨退出

queue.task_done() 說明:

queue.join()的作用是讓主程序阻塞等待隊列完成,就結束退出,但是怎么讓主程序知道隊列已經全部取出并且完成呢?queue.get() 只能讓主程序知道隊列取完了,但不代表隊列里的任務都完成,所以程序需要調用queue.task_done() 告訴主程序,又一個任務完成了,直到全部任務完成,主程序退出

輸出結果

index 1, curent: <Thread(Thread-2, started daemon 139652180764416)> index 0, curent: <Thread(Thread-1, started daemon 139652189157120)> index 2, curent: <Thread(Thread-3, started daemon 139652172371712)> index 4, curent: <Thread(Thread-1, started daemon 139652189157120)> index 3, curent: <Thread(Thread-2, started daemon 139652180764416)> index 5, curent: <Thread(Thread-3, started daemon 139652172371712)> index 6, curent: <Thread(Thread-1, started daemon 139652189157120)> index 7, curent: <Thread(Thread-2, started daemon 139652180764416)> index 8, curent: <Thread(Thread-3, started daemon 139652172371712)> index 9, curent: <Thread(Thread-1, started daemon 139652189157120)> finish

可以看到所有任務都是在這幾個線程中完成Thread-(1-3)

3.線程池原理

線程池基本原理: 我們把任務放進隊列中去,然后開N個線程,每個線程都去隊列中取一個任務,執行完了之后告訴系統說我執行完了,然后接著去隊列中取下一個任務,直至隊列中所有任務取空,退出線程。

上面這個例子生成一個有3個線程的線程池,每個線程都無限循環阻塞讀取Queue隊列的任務所有任務都只會讓這3個預生成的線程來處理。

具體工作描述如下:

  • 創建Queue.Queue()實例,然后對它填充數據或任務
  • 生成守護線程池,把線程設置成了daemon守護線程
  • 每個線程無限循環阻塞讀取queue隊列的項目item,并處理
  • 每次完成一次工作后,使用queue.task_done()函數向任務已經完成的隊列發送一個信號
  • 主線程設置queue.join()阻塞,直到任務隊列已經清空了,解除阻塞,向下執行

這個模式下有幾個注意的點:

  • 將線程池的線程設置成daemon守護進程,意味著主線程退出時,守護線程也會自動退出,如果使用默認

  • daemon=False的話, 非daemon的線程會阻塞主線程的退出,所以即使queue隊列的任務已經完成
    線程池依然阻塞無限循環等待任務,使得主線程也不會退出。

  • 當主線程使用了queue.join()的時候,說明主線程會阻塞直到queue已經是清空的,而主線程怎么知道queue已經是清空的呢?就是通過每次線程queue.get()后并處理任務后,發送queue.task_done()信號,queue的數據就會減1,直到queue的數據是空的,queue.join()解除阻塞,向下執行。

  • 這個模式主要是以隊列queue的任務來做主導的,做完任務就退出,由于線程池是daemon的,所以主退出線程池所有線程都會退出。 有別于我們平時可能以隊列主導thread.join()阻塞,這種線程完成之前阻塞主線程??葱枨笫褂媚膫€join():
    如果是想做完一定數量任務的隊列就結束,使用queue.join(),比如爬取指定數量的網頁
    如果是想線程做完任務就結束,使用thread.join()

4.示例:使用線程池寫web服務器

''' 學習中遇到問題沒人解答?小編創建了一個Python學習交流群:711312441 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' import socket import threading from threading import Thread import threading import sys import time import random from Queue import Queuehost = '' port = 8888 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((host, port)) s.listen(3)class ThreadPoolManger():"""線程池管理器"""def __init__(self, thread_num):# 初始化參數self.work_queue = Queue()self.thread_num = thread_numself.__init_threading_pool(self.thread_num)def __init_threading_pool(self, thread_num):# 初始化線程池,創建指定數量的線程池for i in range(thread_num):thread = ThreadManger(self.work_queue)thread.start()def add_job(self, func, *args):# 將任務放入隊列,等待線程池阻塞讀取,參數是被執行的函數和函數的參數self.work_queue.put((func, args))class ThreadManger(Thread):"""定義線程類,繼承threading.Thread"""def __init__(self, work_queue):Thread.__init__(self)self.work_queue = work_queueself.daemon = Truedef run(self):# 啟動線程while True:target, args = self.work_queue.get()target(*args)self.work_queue.task_done()# 創建一個有4個線程的線程池 thread_pool = ThreadPoolManger(4)# 處理http請求,這里簡單返回200 hello world def handle_request(conn_socket):recv_data = conn_socket.recv(1024)reply = 'HTTP/1.1 200 OK \r\n\r\n'reply += 'hello world'print 'thread %s is running ' % threading.current_thread().nameconn_socket.send(reply)conn_socket.close()# 循環等待接收客戶端請求 while True:# 阻塞等待請求conn_socket, addr = s.accept()# 一旦有請求了,把socket扔到我們指定處理函數handle_request處理,等待線程池分配線程處理thread_pool.add_job(handle_request, *(conn_socket, ))s.close()

運行進程

[master][/data/web/advance_python/socket]$ python sock_s_threading_pool.py # 查看線程池狀況 [master][/data/web/advance_python/socket]$ ps -eLf|grep sock_s_threading_pool lisa+ 27488 23705 27488 0 5 23:22 pts/30 00:00:00 python sock_s_threading_pool.py lisa+ 27488 23705 27489 0 5 23:22 pts/30 00:00:00 python sock_s_threading_pool.py lisa+ 27488 23705 27490 0 5 23:22 pts/30 00:00:00 python sock_s_threading_pool.py lisa+ 27488 23705 27491 0 5 23:22 pts/30 00:00:00 python sock_s_threading_pool.py lisa+ 27488 23705 27492 0 5 23:22 pts/30 00:00:00 python sock_s_threading_pool.py# 跟我們預期一樣一共有5個線程,一個主線程,4個線程池線程

這個線程池web服務器編寫框架包括下面幾個組成部分及步驟:

  • 定義線程池管理器ThreadPoolManger,用于創建并管理線程池,提供add_job()接口,給線程池加任務
  • 定義工作線程ThreadManger, 定義run()方法,負責無限循環工作隊列,并完成隊列任務
  • 定義socket監聽請求s.accept() 和處理請求 handle_requests() 任務。
  • 初始化一個4個線程的線程池,都阻塞等待這讀取隊列queue的任務
  • 當socket.accept()有請求,則把conn_socket做為參數,handle_request方法,丟給線程池,等待線程池分配線程處理

5.GIL 對多線程的影響

因為Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先獲得GIL鎖,然后,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把所有線程的執行代碼都給上了鎖,所以,多線程在Python中只能交替執行,即使100個線程跑在100核CPU上,也只能用到1個核。

但是對于IO密集型的任務,多線程還是起到很大效率提升,這是協同式多任務
當一項任務比如網絡 I/O啟動,而在長的或不確定的時間,沒有運行任何 Python 代碼的需要,一個線程便會讓出GIL,從而其他線程可以獲取 GIL 而運行 Python。這種禮貌行為稱為協同式多任務處理,它允許并發;多個線程同時等待不同事件。

兩個線程在同一時刻只能有一個執行 Python ,但一旦線程開始連接,它就會放棄 GIL ,這樣其他線程就可以運行。這意味著兩個線程可以并發等待套接字連接,這是一件好事。在同樣的時間內它們可以做更多的工作。

6.線程池要設置為多少?

服務器CPU核數有限,能夠同時并發的線程數有限,并不是開得越多越好,以及線程切換是有開銷的,如果線程切換過于頻繁,反而會使性能降低

線程執行過程中,計算時間分為兩部分:

  • CPU計算,占用CPU
  • 不需要CPU計算,不占用CPU,等待IO返回,比如recv(), accept(), sleep()等操作,具體操作就是比如
  • 訪問cache、RPC調用下游service、訪問DB,等需要網絡調用的操作

那么如果計算時間占50%, 等待時間50%,那么為了利用率達到最高,可以開2個線程:
假如工作時間是2秒, CPU計算完1秒后,線程等待IO的時候需要1秒,此時CPU空閑了,這時就可以切換到另外一個線程,讓CPU工作1秒后,線程等待IO需要1秒,此時CPU又可以切回去,第一個線程這時剛好完成了1秒的IO等待,可以讓CPU繼續工作,就這樣循環的在兩個線程之前切換操作。

那么如果計算時間占20%, 等待時間80%,那么為了利用率達到最高,可以開5個線程:
可以想象成完成任務需要5秒,CPU占用1秒,等待時間4秒,CPU在線程等待時,可以同時再激活4個線程,這樣就把CPU和IO等待時間,最大化的重疊起來

抽象一下,計算線程數設置的公式就是:
N核服務器,通過執行業務的單線程分析出本地計算時間為x,等待時間為y,則工作線程數(線程池線程數)設置為 N*(x+y)/x,能讓CPU的利用率最大化。
由于有GIL的影響,python只能使用到1個核,所以這里設置N=1

總結

以上是生活随笔為你收集整理的Python学习:线程池原理及实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。