很多情況下我們會遇到編程模型選擇的問題:多進程 or 多線程 ? 下面簡要介紹下兩者區別:
多進程 | 多線程 | |
資源 | 進程是資源分配的基本單位,獨占用整個進程所有資源 |
進程內所有線程共享進程資源
|
通信 | 需要借助共享內存、管道、信號量、socket等方式實現 | 由於線程資源共享,很容易實現各線程間消息通信 |
編程 | 編程調試簡單,可靠性高,創建銷毀系統開銷大 | CPU調度的基本單位,切換速度快,資源訪問互斥、同步導致編程復雜度增加,同時也不方便調試 |
信號 | 每個進程獨立控制信號 | 進程內所有線程共享信號處理函數,除了SIGSEGV,SIGALRM這樣的信號會直接發送給調用線程,其余的信號都默認交給主線程。信號處理邏輯復雜 |
上面表格中寫出了多線程、多進程的基本區別,除了上述內容我們還有一些需要關注的點:
- 由於所有線程共享進程的資源,因此所有線程能夠打開的最大文件描述符數(包含socket)之和等於進程支持的最大文件描述符數;所有線程棧(默認8MB)大小的總和等於進程訪問的最大地址空間,因此如果線程創建了一個超大的局部數組或者其他結構,可能會導致所有線程所占的地址空間總和超出進程地址空間的范圍進而觸發SIGSEGV段錯誤,致使整個程序崩潰。
- 多線程的優勢在於共享進程中的全局資源以及堆區資源,極大方便了各個線程間信息的交換;在帶來方便的同時,為了保證每個線程在訪問這些共享資源時的正常訪問,需要對這些共享資源的訪問添加互斥、同步機制,這就增加了多線程程序的開發、調試難度,程序的穩定性難以把控。
- 信號處理在多線程程序開發時,也是需要考慮的一點;建議在多線程程序中,創建單獨的線程利用sigwait等函數同步處理信號,其余線程直接屏蔽信號,這樣避免了注冊信號處理函數這種異步處理方式導致的問題。
之前看到很多人疑問,為什么高性能的nginx、redis要采用多進程模型而不是多線程模型?
我來根據上面對多進程、多線程的分析來嘗試解答下:
- nginx、redis每個單獨的進程都可以獨占資源,通常情況下每個服務器會開幾十個nginx、redis進程,這樣如果采用多線程的模式,各個線程能夠利用的資源就會受到一些限制。諸如:ulimit -n 命令展示的每個進程最多可以打開的文件數(這個可以改的;另外nginx文件讀寫操作不多,這里不是問題)這樣的限制。
- nginx中除了master需要跟worker通過管道進行通信,worker之間不需要通信,而且每個worker的功能都一樣,屬於常駐進程。在這種場景下多線程的優勢體現不出來,而且也可以避免多線程在編程時需要考慮資源訪問互斥、同步等問題帶來的編程復雜度的提升,以及可能帶來的調試困難。(PS:這句話的同步、互斥並非指線程間消息傳遞等操作,這種操作就像評論中@sevencatwang 說的,在多進程中開銷更大)
- nginx采用多進程的方式,既可以避免因某個線程故障導致整個服務不可用的問題,也可以實現配置熱加載,不停服升級版本。
注:nginx是有線程池的選項的,但是該線程池也是在每個worker中用於處理業務的,對於連接的處理還是由主線程來完成的;在這里多線程只是對nginx多進程的一個補充,而不是替代。
redis目前在執行aof、bgsave等操作時,由於是單線程,所以會導致請求延時臨時增大,服務不穩定。redis的作者希望在將來引入線程來處理這種IO密集型的操作