多進程、多線程以及如何選擇?

關於線程:
首先關於多線程多進程,看一張圖:
1
基本上把線程,進程區別說清楚了

提到線程,就不得不提線程同步的問題,我專門歸納了一篇文章:
線程同步常用方式與區別

線程2個性質,
線程安全:概念比較直觀。一般說來,一個函數被稱爲線程安全的,當且僅當被多個併發線程反覆調用時,它會一直產生正確的結果。
可重入:概念基本沒有比較正式的完整解釋,但是它比線程安全要求更嚴格。根據經驗,所謂“重入”,常見的情況是,程序執行到某個函數foo()時,收到信號,於是暫停目前正在執行的函數,轉到信號處理函數,而這個信號處理函數的執行過程中,又恰恰也會進入到剛剛執行的函數foo(),這樣便發生了所謂的重入。此時如果foo()能夠正確的運行,而且處理完成後,之前暫停的foo()也能夠正確運行,則說明它是可重入的。
線程安全的條件:
要確保函數線程安全,主要需要考慮的是線程之間的共享變量。屬於同一進程的不同線程會共享進程內存空間中的全局區和堆,而私有的線程空間則主要包括棧和寄存器。因此,對於同一進程的不同線程來說,每個線程的局部變量都是私有的,而全局變量、局部靜態變量、分配於堆的變量都是共享的。在對這些共享變量進行訪問時,如果要保證線程安全,則必須通過加鎖的方式。

可重入的判斷條件:
要確保函數可重入,需滿足一下幾個條件:
1、不在函數內部使用靜態或全局數據
2、不返回靜態或全局數據,所有數據都由函數的調用者提供。
3、使用本地數據,或者通過製作全局數據的本地拷貝來保護全局數據。
4、不調用不可重入函數。

可重入與線程安全並不等同,一般說來,可重入的函數一定是線程安全的,但反過來不一定成立。
比如:strtok函數是既不可重入的,也不是線程安全的;加鎖的strtok不是可重入的,但線程安全;而strtok_r既是可重入的,也是線程安全的。

如果我們的線程函數不是線程安全的,那在多線程調用的情況下,可能導致的後果是顯而易見的——共享變量的值由於不同線程的訪問,可能發生不可預料的變化,進而導致程序的錯誤,甚至崩潰。

關於進程:
關於IPC(進程間通信)

由於多進程要併發協調工作,進程間的同步,通信是在所難免的。
稍微列舉一下linux常見的IPC方式:
匿名管道(Pipe)及有名管道(named pipe/fifo):管道可用於具有親緣關係進程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關係進程間的通信;
信號(Signal):信號是比較複雜的通信方式,用於通知接受進程有某種事件發生,除了用於進程間通信外,進程還可以發送信號給進程本身;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又能夠統一對外接口,用sigaction函數重新實現了signal函數);
報文(Message)隊列(消息隊列):消息隊列是消息的鏈接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程可以向隊列中添加消息,被賦予讀權限的進程則可以讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺點。
共享內存:使得多個進程可以訪問同一塊內存空間,是最快的可用IPC形式。是針對其他通信機制運行效率較低而設計的。往往與其它通信機制,如信號量結合使用,來達到進程間的同步及互斥。
信號量(semaphore):主要作爲進程間以及同一進程不同線程之間的同步手段。
套接字(Socket):更爲一般的進程間通信機制,可用於不同機器之間的進程間通信。起初是由Unix系統的BSD分支開發出來的,但現在一般可以移植到其它類Unix系統上:Linux和System V的變種都支持套接字。
或許你會有疑問,那多線程間要通信,應該怎麼做?前面已經說了,多數的多線程都是在同一個進程下的,它們共享該進程的全局變量,我們可以通過全局變量來實現線程間通信。如果是不同的進程下的2個線程間通信,直接參考進程間通信。

選擇哪個?
1)需要頻繁創建銷燬的優先用線程
這種原則最常見的應用就是Web服務器了,來一個連接建立一個線程,斷了就銷燬線程,要是用進程,創建和銷燬的代價是很難承受的

2)需要進行大量計算的優先使用線程
所謂大量計算,當然就是要耗費很多CPU,切換頻繁了,這種情況下線程是最合適的。
這種原則最常見的是圖像處理、算法處理。

3)強相關的處理用線程,弱相關的處理用進程

什麼叫強相關、弱相關?理論上很難定義,給個簡單的例子就明白了。
一般的Server需要完成如下任務:消息收發、消息處理。“消息收發”和“消息處理”就是弱相關的任務,而“消息處理”裏面可能又分爲“消息解碼”、“業務處理”,這兩個任務相對來說相關性就要強多了。因此“消息收發”和“消息處理”可以分進程設計,“消息解碼”、“業務處理”可以分線程設計。

當然這種劃分方式不是一成不變的,也可以根據實際情況進行調整。

4)可能要擴展到多機分佈的用進程,多核分佈的用線程

5)都滿足需求的情況下,用你最熟悉、最拿手的方式

至於“數據共享、同步”、“編程、調試”、“可靠性”這幾個維度的所謂的“複雜、簡單”應該怎麼取捨,我只能說:沒有明確的選擇方法。但我可以告訴你一個選擇原則:如果多進程和多線程都能夠滿足要求,那麼選擇你最熟悉、最拿手的那個。

需要提醒的是:雖然我給了這麼多的選擇原則,但實際應用中基本上都是“進程+線程”的結合方式,千萬不要真的陷入一種非此即彼的誤區。

消耗資源:
從內核的觀點看,進程的目的就是擔當分配系統資源(CPU時間、內存等)的基本單位。線程是進程的一個執行流,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。

線程,它們彼此之間使用相同的地址空間,共享大部分數據,啓動一個線程所花費的空間遠遠小於啓動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個數據可能會有較大的區別。

通訊方式:
進程之間傳遞數據只能是通過通訊的方式,即費時又不方便。線程時間數據大部分共享(線程函數內部不共享),快捷方便。但是數據同步需要鎖對於static變量尤其注意

線程自身優勢:
提高應用程序響應;使多CPU系統更加有效操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上
改善程序結構。一個既長又複雜的進程可以考慮分爲多個線程,成爲幾個獨立或半獨立的運行部分,這樣的程序會利於理解和修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章