標籤:style blog http color 使用 strong 檔案 資料
基於進程的並發基本模型
在TCP伺服器編程中,多進程並發伺服器通常由主進程負責串連的建立,然後fork出子進程,負責該串連剩下的行為,直到關閉。
關於多進程並發伺服器有幾點重要的內容:
通常伺服器會運行很長時間,因此必須要包括一個SIGCHLD處理常式,來回收僵死子進程的資源。因為當SIGCHLD處理常式執行時,SIGCHLD訊號是阻塞的,而Unix訊號是不排隊的,所以SIGCHLD處理常式必須準備好回收多個僵死子進程的資源。
父進程在fork調用後,將串連交給子進程處理,父子進程必須關閉他們各自不用的fd拷貝,對父進程尤為重要。
因為通訊端的檔案表表項中的引用計數,直到父子進程的fd都關閉了,到用戶端的串連才會終止。
優劣
進程有獨立的地址空間,既是優點也是缺點,這樣一來一個進程不可能不小心覆蓋另一個進程的虛擬儲存空間,另一方面,獨立的地址空間使得進程共用狀態資訊變得更加困難。
基於進程的並發能力畢竟有限,想想系統下成百上千個進程的情況下,系統還能運轉多快,更別說幾萬了。
基於I/O多工並發何為多工
雖然瞭解I/O多工,但一直以來,對這個名字很好奇,什麼叫多工?為什麼稱為IO多工?
通過查看維基上的定義算是有了一個初步的認識,以下是在維基中摘錄的部分介紹:
多工(Multiplexing,又稱“多工”)是一個通訊和電腦網路領域的專業術語,通常表示在一個通道上傳輸多路訊號或資料流的過程和技術。因為多工能夠將多個低速通道整合到一個高速通道進行傳輸,從而有效地利用了高速通道。通過使用多工,通訊電訊廠商可以避免維護多條線路,從而有效地節約運營成本。
多工抽象模型
首先,各個低速通道的訊號通過多工器(MUX,多工器)組合成一路可以在高速通道傳輸的訊號。在這個訊號通過高速通道到達接收端之後,再由分路器(DEMUX,解多工器)將高速通道傳輸的訊號轉換成多個低速通道的訊號,並且轉寄給對應的低速通道。查看原文請點擊。
反過來看I/O多工,就是在一個線程中,處理多路I/O。對應於多工原則,線程處理能力必須快,I/O需要儘可能的拆分成小的單元,每個單元盡量短的佔用線程的時間,如果某個I/O處理單元長時間佔用線程的處理時間,就會導致其他I/O得不到及時的處理。
I/O多工基本思想
比如我的程式需要從多個I/O上等待資料到來並讀取
pipe fd1
tcp socket1 fd2
tcp socket2 fd3
udp socket fd4
那麼定義一個數組{fd1,fd2,fd3,fd4}儲存好,並通知核心,我對這幾個fd的輸入事件感興趣,此時核心會說你等著吧,等他們誰有資料了我會通知你,然後你就睡眠了。
當某個fd有資料到達可讀時,核心會立即通知你,嘿,有資料來了,快醒了。於是你趕快醒來,找到哪個fd有資料,然後接收處理,處理完了,一輪結束繼續告知核心感興趣的事件,然後睡眠。
linux支援I/O多工系統調用有select、poll、epoll。
I/O多工優缺點
I/O多工可以用於事件驅動的編程,事件驅動設計,比基於進程的設計給了程式員給多的多程式行為的控制。例如一個並發伺服器,需要為某些用戶端提供特殊化的服務,基於事件驅動的設計,可以在流程裡面有選擇的處理。而基於進程的設計,需要在fork進程之前,選擇子進程的行為,如果特殊化的服務比較多,基於進程的設計處理起來是很困難的。
基於I/O多工伺服器是運行在單一進程上下文中,因此每個邏輯流都能訪問該進程的全部地址空間,使得流之間共用資料變得很容易。
基於I/O多工伺服器在GDB調試時要比多進程方便。
基於驅動設計常常比基於進程的設計要高效的多,因為他們並不需要進程環境切換。
事件驅動設計的一個明顯的缺點是編碼複雜,隨著並發顆粒度(每個邏輯流在每個時間片上執行的指令數量)減小,複雜性還會上升。並發粒度控制不好,很容易導致忙於處理某個邏輯流,其他流得不到處理。
基於線程的並發
基於線程的邏輯流結合了基於進程和基於I/O多工的流的特性。同進程一樣,線程由核心自動調度,並且核心通過一個整數ID來識別線程。同基於I/O多工流一樣,多個線程運行在單一進程的上下文中,因此共用這個進程虛擬位址空間的整個內容,包括他的代碼、資料、堆、共用庫和開啟的檔案。
基本多執行緒模式
整個模型類似與基於多進程的設計,主線程不斷的等待串連請求,然後建立一個線程處理該請求。
這個模型中有個微妙的問題,主線程建立某一新串連的fd,如果直接傳遞給子線程,那麼很有可能在子線程處理前,主線程建立了另一個串連,並更新了fd,那麼不幸的結果就是,現在兩個線程在同一個描述符上執行輸入輸出。以下是範例程式碼:
1 connfd = accept(listenfd, &clientaddr, &clientlen);2 pthread_create(&tid, NULL, thread, &connfd);
另一個問題是線上程常式中避免儲存空間泄露,要麼顯示回收記憶體,要麼必須分離記憶體。
基於預線程化的生產者-消費者模型
伺服器由一個主線程和一組工作者線程構成,主線程不斷的接受來自用戶端的串連請求,並將得到的串連描述符放在一個有限緩衝區中。每一個工作者線程反覆的從共用緩衝區中取出描述符,為用戶端服務,然後等待下一個描述符。
I/O多工不時編寫事件驅動程式的唯一方法,上述模型實際上也是一個事件驅動伺服器,帶有主線程和背景工作執行緒的簡單狀態機器。
多線程並發編程中需要考慮的一些問題參考《深入理解電腦系統》12.7節,如果你有更好的書,記得推薦給我喲。
進階並發編程
參考NGINX並發處理模型的設計