Python實(shí)現(xiàn)線程池工作模式的案例詳解
本文章基于蘋果樹病蟲害預(yù)測模型,自定義應(yīng)用層通信邏輯,設(shè)計服務(wù)器與客戶機(jī)??蛻魴C(jī)向服務(wù)器發(fā)送圖像數(shù)據(jù),服務(wù)器回送預(yù)測結(jié)果。為增強(qiáng)服務(wù)器的可靠性與可擴(kuò)展性,服務(wù)器端采用線程池工作模式。為了增強(qiáng)客戶機(jī)的可操作性,客戶機(jī)采用PyQt5完成圖形化界面設(shè)計。
01、客戶機(jī)/服務(wù)器通信邏輯
客戶機(jī)與服務(wù)器通信邏輯如圖1所示。
■圖1 智能桌面App的客戶機(jī)/服務(wù)器通信邏輯
02、數(shù)據(jù)交換協(xié)議
客戶機(jī)與服務(wù)器之間一次信息往返的協(xié)議會話過程,定義為圖2所示的邏輯時序。
■ 圖2 應(yīng)用層通信協(xié)議
協(xié)議會話邏輯解析:
(1)消息交換基于消息頭機(jī)制。消息頭中包含消息類型和消息長度。消息類型包含圖像消息與下線消息。
(2)用Json格式的數(shù)據(jù)表示消息頭。圖像數(shù)據(jù)用base64編碼與解碼。
(3)發(fā)送數(shù)據(jù)分兩個步驟完成,首先發(fā)送消息頭,然后發(fā)送消息內(nèi)容。
(4)接收數(shù)據(jù)分兩個步驟完成,首先接收消息頭,然后接收消息內(nèi)容。
消息頭的結(jié)構(gòu)設(shè)計如圖3所示,消息頭的固定長度為128字節(jié),包含消息類型(msg_type)與消息內(nèi)容長度(msg_len)兩個字段。
■圖3 消息頭的結(jié)構(gòu)
消息類型包括:
(1)CLIENT_IMAGE:表示收到來自客戶機(jī)的圖像數(shù)據(jù)。
(2)CLIENT_MESSAGE:表示收到來自客戶機(jī)的下線消息。
消息內(nèi)容長度用消息包含的字符數(shù)表示。對于圖像數(shù)據(jù)而言,因為采用base64編碼,其傳輸?shù)臄?shù)據(jù)也是字符消息。
消息頭的長度在服務(wù)器與客戶機(jī)兩端均約定為128字節(jié),用常量MSG_HEADER_LEN定義。發(fā)送消息頭之前,需要檢查消息的長度,如果不足128字節(jié),其左側(cè)用字節(jié)型空格字符填充。
03、服務(wù)器主體邏輯
根據(jù)圖1描述的服務(wù)器邏輯,完成服務(wù)器的主體邏輯設(shè)計,如程序段P7.1所示。
第32~39行定義服務(wù)器端的主循環(huán),處理客戶機(jī)連接,采用的是一客戶一線程模式。服務(wù)器會話線程定義為handle_client模塊,主線程向會話線程傳遞三個參數(shù):
(1)client_socket: 會話套接字
(2)client_addr: 客戶機(jī)地址
(3)model: 用于預(yù)測的智能模型
運(yùn)行服務(wù)器程序,觀察輸出結(jié)果,此時服務(wù)器雖然處于偵聽連接的狀態(tài),但是由于handle_client模塊還沒有實(shí)現(xiàn),故無法處理來自客戶機(jī)的各種消息。
04、服務(wù)器會話線程
服務(wù)器會話線程包括接收數(shù)據(jù)與發(fā)送數(shù)據(jù)兩個模塊,對應(yīng)圖1中的內(nèi)循環(huán)。服務(wù)器完成數(shù)據(jù)接收后,需要回送預(yù)測結(jié)果或者確認(rèn)消息給客戶機(jī),所以將接收數(shù)據(jù)與發(fā)送數(shù)據(jù)的邏輯定義在同一函數(shù)模塊handle_client中,收發(fā)數(shù)據(jù)的邏輯流程如圖4所示。
■圖4 服務(wù)器收發(fā)數(shù)據(jù)會話線程邏輯
會話線程的主邏輯是一個循環(huán),循環(huán)條件為遠(yuǎn)程客戶機(jī)是否結(jié)束會話,邏輯流程解析如下:
(1)如果客戶機(jī)斷開了與服務(wù)器的連接,會話線程結(jié)束。
(2)在連接正常的情況下,服務(wù)器首先接收來自客戶機(jī)的消息頭,解析消息頭,根據(jù)消息類型,分為一般消息與圖像消息。
(3)如果是圖像消息,則通過一個循環(huán),根據(jù)圖像的大小完成數(shù)據(jù)接收,然后經(jīng)過base64解碼、圖像變換(調(diào)整顏色模式、歸一化、縮放)、模型預(yù)測、重構(gòu)預(yù)測結(jié)果、定義消息頭、回送消息頭、回送預(yù)測結(jié)果。回到步驟(1)。
(4)如果是一般消息,則繼續(xù)判斷是否為下線消息。
(5)如果是下線消息,則更新連接數(shù)量,定義下線消息(原消息加上時間戳),定義消息頭,回送消息頭,回送消息內(nèi)容,會話線程結(jié)束。
(6)如果不是下線消息,則做其他消息處理,為簡化設(shè)計,其他消息處理模塊暫不編程,留作擴(kuò)展。回到步驟(1)。
會話線程handle_client的邏輯實(shí)現(xiàn)如程序段P7.2所示。
第46行–第51行定義的循環(huán)結(jié)構(gòu),根據(jù)圖像數(shù)據(jù)的長度msg_len完成數(shù)據(jù)接收工作。
運(yùn)行服務(wù)器程序,輸出結(jié)果為:
服務(wù)器開始在('192.168.0.102', 5050)偵聽...
待客戶機(jī)程序完成后,再做聯(lián)合測試。
05、客戶機(jī)主體邏輯
新建主程序MyClient.py。根據(jù)圖1描述的客戶機(jī)邏輯,完成客戶機(jī)的主體邏輯設(shè)計,其主要模塊如圖5所示。
模塊send_image_data發(fā)送圖像數(shù)據(jù),模塊send_down_msg發(fā)送下線消息,模塊recv_message是用于接收服務(wù)器消息的會話線程,類模塊GUI(QMainWindow)負(fù)責(zé)構(gòu)建客戶機(jī)圖形化界面。主程序完成主控邏輯設(shè)計。
■圖5 客戶機(jī)程序模塊結(jié)構(gòu)
客戶機(jī)的消息結(jié)構(gòu)定義如圖3所示,與服務(wù)器保持一致。消息的收發(fā)邏輯,如圖2所示,亦與服務(wù)器保持一致。
客戶機(jī)主體邏輯如程序段P7.3所示。
首先運(yùn)行服務(wù)器程序,然后運(yùn)行測試客戶機(jī)程序。目前客戶機(jī)還做不了具體工作,輸入字符Q退出客戶機(jī)主循環(huán)。
06、客戶機(jī)發(fā)送數(shù)據(jù)
客戶機(jī)向服務(wù)器發(fā)送的數(shù)據(jù)有兩種類型,一是圖像數(shù)據(jù),一是下線消息。發(fā)送圖像數(shù)據(jù)的流程如圖6所示。
■圖6 發(fā)送圖像數(shù)據(jù)流程
程序段P7.4描述了發(fā)送圖像數(shù)據(jù)模塊send_image_data的完整邏輯。
07、客戶機(jī)接收數(shù)據(jù)
客戶機(jī)定義了線程函數(shù)recv_message,用于接收兩類數(shù)據(jù),一是普通消息(下線消息等),二是預(yù)測消息(預(yù)測結(jié)果)。消息處理流程如圖7所示,分步描述如下。
(1)進(jìn)入消息循環(huán),接收消息頭。
(2)如果消息頭為空,轉(zhuǎn)到步驟(1)。
(3)如果消息頭非空,則解析消息頭,獲取消息類型與消息長度。
(4)如果是普通消息,則接收消息內(nèi)容,進(jìn)一步判斷是否為下線消息。
(5)如果是下線消息,則跳出消息循環(huán),轉(zhuǎn)到步驟(9)。
(6)如果非下線消息,則轉(zhuǎn)到步驟(1)。
(7)如果不是普通消息,則判斷是否為預(yù)測消息,如果不是預(yù)測消息,則轉(zhuǎn)到步驟(1)。
(8)如果是預(yù)測消息,則接收消息內(nèi)容,解析消息內(nèi)容,將預(yù)測結(jié)果存入隊列中,顯示預(yù)測結(jié)果。轉(zhuǎn)到步驟(1)。
(9)顯示下線消息,消息接收線程結(jié)束。
■圖7 客戶機(jī)接收消息邏輯流程
程序段P7.6描述了接收消息線程函數(shù)recv_message的完整邏輯。
將\dataset\images目錄下的圖像文件Test_0.jpg、Test_7.jpg拷貝到根目錄下。
運(yùn)行服務(wù)器程序,然后運(yùn)行客戶機(jī)程序,做聯(lián)合測試。
客戶機(jī)輸入待遇測的圖像文件名稱Test_0.jpg,回車后發(fā)送圖像數(shù)據(jù),服務(wù)器返回預(yù)測結(jié)果??蛻魴C(jī)輸入字符Q,結(jié)束客戶機(jī)。完成此次客戶機(jī)與服務(wù)器的通信后,服務(wù)器與客戶機(jī)的狀態(tài)信息如圖8所示。
■圖8 客戶機(jī)與服務(wù)器聯(lián)合測試
此時服務(wù)器工作于一客戶一線程模式,啟動多個客戶端,可做聯(lián)合測試。
08、客戶機(jī)界面設(shè)計
為了增強(qiáng)客戶機(jī)的可操作性,基于PyQt5框架為客戶機(jī)設(shè)計圖形化界面,界面布局及其控件名稱如圖9所示。
■圖9 客戶機(jī)圖形化界面布局
定義圖形化界面類GUI(QMainWindow)封裝圖9所示的控件及其事件函數(shù)。
運(yùn)行服務(wù)器,然后運(yùn)行客戶機(jī),從chapter7的根目錄中加載圖像Test_0.jpg,觀察圖像特點(diǎn)。然后單擊“預(yù)測”按鈕,觀察服務(wù)器反饋的預(yù)測結(jié)果,如圖10所示。
■圖10 客戶機(jī)圖形化界面測試結(jié)果
09、線程池
服務(wù)器現(xiàn)有的工作模式為一客戶一線程,即為每一個連接到服務(wù)器的客戶機(jī)創(chuàng)建獨(dú)立的會話線程,當(dāng)客戶機(jī)并發(fā)量較大時,服務(wù)器往往面臨資源枯竭的挑戰(zhàn)。
線程池模式可以有效平衡服務(wù)器負(fù)載能力,與一客戶一線程模式相比,其主要優(yōu)點(diǎn)有:
(1)通過重用已存在的線程,降低線程創(chuàng)建和銷毀造成的額外消耗。
(2)提高系統(tǒng)響應(yīng)速度,當(dāng)有新任務(wù)到達(dá)時,通過復(fù)用已存在的線程便能立即執(zhí)行,無需等待新線程的創(chuàng)建。
(3)控制資源消耗,將并發(fā)線程數(shù)量限制在合理的區(qū)間。
(4)針對工作線程提供了更多的控制能力,例如線程延時、定時等。
Python的線程池定義在concurrent.futures包中,使用ThreadPoolExecutor類創(chuàng)建線程池。線程池調(diào)度任務(wù)過程如圖11所示。
■圖11 線程池調(diào)度任務(wù)示意圖
將一客戶一線程模式修改為線程池模式,只需做以下改動:
(1)導(dǎo)入線程池類ThreadPoolExecutor。在服務(wù)器端添加語句:
from concurrent.futures import ThreadPoolExecutor # 線程池類
(2)在服務(wù)器主線程的while循環(huán)前面添加創(chuàng)建線程池的語句:
pool = ThreadPoolExecutor(max_workers=5) # 創(chuàng)建線程池,指定工作線程數(shù)量為5
此處如果省略參數(shù)max_workers,則線程池默認(rèn)工作線程數(shù)量是CPU數(shù)量的5倍。考慮到線程池往往應(yīng)用于需要大量I/O交換的場景,而不是CPU計算密集型的場景,故工作線程的數(shù)量應(yīng)該超過CPU的數(shù)量。
(3)用線程池調(diào)度語句替換原有的線程創(chuàng)建語句。
# 建立與客戶機(jī)會話的線程,一客戶一線程 client_thread = threading.Thread(target=handle_client, args=(new_socket, new_addr, model)) client_thread.start()
替換為:
pool.submit(handle_client,new_socket, new_addr, model) # 創(chuàng)建線程任務(wù),提交到線程池
(4)在主程序末尾,while循環(huán)外部,添加關(guān)閉線程池的語句,釋放資源:
pool.shutdown(wait=True) # 關(guān)閉線程池
執(zhí)行shutdown后,線程池將不再接受新任務(wù)。參數(shù)wait默認(rèn)為True,表示關(guān)閉線程池之前需要等待所有工作線程結(jié)束。
10、聯(lián)合測試
為便于觀察,將服務(wù)器線程池的工作線程數(shù)量調(diào)整為2。啟動服務(wù)器,然后啟動四個客戶機(jī),標(biāo)識為客戶機(jī)1、客戶機(jī)2、客戶機(jī)3、客戶機(jī)4。
四個客戶機(jī)從dataset\images目錄中選擇四幅不同的測試圖片,
假定客戶機(jī)1選擇的圖片是Test_17.jpg,客戶機(jī)2選擇的是Test_152.jpg,客戶機(jī)3選擇的是Test_190.jpg,客戶機(jī)4選擇的是Test_1572.jpg,然后依次點(diǎn)擊客戶機(jī)1、客戶機(jī)2、客戶機(jī)3、客戶機(jī)4的“預(yù)測”按鈕,觀察預(yù)測結(jié)果。
可以看到,只有客戶機(jī)1、客戶機(jī)2立即反饋了預(yù)測結(jié)果,而客戶機(jī)3、客戶機(jī)4雖然已經(jīng)連接到服務(wù)器,卻并沒有立即得到預(yù)測結(jié)果,原因是服務(wù)器線程池大小為2,客戶機(jī)3、客戶機(jī)4需要在任務(wù)隊列等待。
客戶機(jī)1顯示結(jié)果如圖12所示。
■圖12 客戶機(jī)1的預(yù)測結(jié)果
客戶機(jī)2顯示結(jié)果圖13所示。
■圖13 客戶機(jī)2的預(yù)測結(jié)果
客戶機(jī)3顯示結(jié)果如圖14所示。由于服務(wù)器線程池大小為2,所以客戶機(jī)1與客戶機(jī)2占用工作線程后,客戶機(jī)3只能進(jìn)入任務(wù)隊列等待。
■圖14 客戶機(jī)3處于等待中
客戶機(jī)4顯示結(jié)果如圖15所示。同樣,客戶機(jī)4也只能進(jìn)入服務(wù)器的任務(wù)隊列等待。
■圖15 客戶機(jī)4處于等待中
關(guān)閉客戶機(jī)1,則會自動釋放客戶機(jī)1占用的工作線程,此時排隊中的客戶機(jī)3會立即得到相應(yīng),其結(jié)果如圖16所示。
■圖16 客戶機(jī)3得到服務(wù)器響應(yīng)
此時只有客戶機(jī)4仍處于等待中。如果繼續(xù)關(guān)閉客戶機(jī)2,則客戶機(jī)4會得到立即響應(yīng),其預(yù)測結(jié)果如圖17所示。
■圖17 客戶機(jī)4得到服務(wù)器響應(yīng)
關(guān)閉客戶機(jī)3、關(guān)閉客戶機(jī)4。整個會話期間,服務(wù)器狀態(tài)監(jiān)控界面的信息提示如下:
仔細(xì)閱讀服務(wù)器的狀態(tài)提示信息,與客戶機(jī)的操作相對照,可以更精準(zhǔn)地把握客戶機(jī)與服務(wù)器的全程會話邏輯。
11、小結(jié)
本文基于Socket通信方法,自定義數(shù)據(jù)交換協(xié)議,圍繞蘋果樹病蟲害識別需求,迭代構(gòu)建了客戶機(jī)/服務(wù)器模式的智能桌面App。圖像數(shù)據(jù)的發(fā)送采用base64編碼方式,消息頭、消息內(nèi)容采用Json數(shù)據(jù)格式。服務(wù)器端采用一客戶一線程和線程池技術(shù)支持并發(fā)訪問,客戶機(jī)采用基于PyQt5的圖像化界面技術(shù)提高其可操作性。基于Socket技術(shù)的網(wǎng)絡(luò)編程,在客戶機(jī)與服務(wù)器兩端提供了更多的設(shè)計靈活性。
到此這篇關(guān)于Python實(shí)現(xiàn)線程池工作模式的文章就介紹到這了,更多相關(guān)Python線程池工作模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python網(wǎng)絡(luò)爬蟲采集聯(lián)想詞示例
這篇文章主要介紹了python網(wǎng)絡(luò)爬蟲采集聯(lián)想詞示例,需要的朋友可以參考下2014-02-02詳解Python中的array數(shù)組模塊相關(guān)使用
數(shù)組并不是Python中內(nèi)置的標(biāo)配數(shù)據(jù)結(jié)構(gòu),不過擁有array模塊我們也可以在Python中使用數(shù)組結(jié)構(gòu),下面我們就來詳解詳解Python中的array數(shù)組模塊相關(guān)使用2016-07-07python中if的基礎(chǔ)用法(if?else和if?not)
if在Python中用作某個條件或值的判斷,下面這篇文章主要給大家介紹了關(guān)于python中if的基礎(chǔ)用法,主要包括if?else和if?not,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09python linecache讀取行更新的實(shí)現(xiàn)
本文主要介紹了python linecache讀取行更新的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03python實(shí)現(xiàn)mask矩陣示例(根據(jù)列表所給元素)
這篇文章主要介紹了python實(shí)現(xiàn)mask矩陣示例(根據(jù)列表所給元素),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07python定時采集攝像頭圖像上傳ftp服務(wù)器功能實(shí)現(xiàn)
本文程序?qū)崿F(xiàn)python定時采集攝像頭圖像上傳ftp服務(wù)器功能,大家參考使用吧2013-12-12python實(shí)現(xiàn)126郵箱發(fā)送郵件
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)126郵箱發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05