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