多線程程序、多進(jìn)程程序是當(dāng)前單機(jī)應(yīng)用常用并行化的手段,線程是可以直接被CPU調(diào)度的執(zhí)行單元,雖然多進(jìn)程程序中每個(gè)進(jìn)程也可以是多線程的,但是本文主要討論的多進(jìn)程程序默認(rèn)是每個(gè)進(jìn)程都有一個(gè)單獨(dú)線程的情況。多線程程序和多進(jìn)程程序,涉及到的線程間和進(jìn)程間的通信、同步原語(yǔ)基本都是相同的,所以兩者的開發(fā)在一定程度上有著高度的相似性,但同時(shí)差異化也十分的明顯,所以高性能程序使用多線程還是多進(jìn)程實(shí)現(xiàn)常常也是爭(zhēng)論的焦點(diǎn)。
雖然自己之前開發(fā)的程序基本都是基于pthreads和C++ std::thread的多線程程序,但是多進(jìn)程程序還是有它相應(yīng)的用武之地的,比如大名鼎鼎的Nginx中master和worker機(jī)制就是采用多進(jìn)程的方式實(shí)現(xiàn)的,所以這里也對(duì)多進(jìn)程和多線程程序的區(qū)別聯(lián)系整理一下,最后順便看看Nginx中master和worker進(jìn)程的管理和實(shí)現(xiàn)機(jī)制,在后續(xù)開發(fā)多進(jìn)程程序的時(shí)候可以直接借鑒使用。
一、多線程和多進(jìn)程程序
Linux中有一句耳熟能詳?shù)脑?mdash;—線程被認(rèn)為是輕量級(jí)的進(jìn)程,在現(xiàn)代操作系統(tǒng)的概念中,進(jìn)程被認(rèn)為是資源管理的最小單元,而線程被認(rèn)為是程序執(zhí)行的最小單元,所以多線程和多進(jìn)程之間的差異基本體現(xiàn)在執(zhí)行單元之間對(duì)資源耦合度的差異。雖然對(duì)于用戶空間而言,最為廣為使用的pthreads線程庫(kù)提供了自己一套線程創(chuàng)建和管理、線程間同步接口,其實(shí)在Linux下面創(chuàng)線程和創(chuàng)建進(jìn)程都是使用clone()系統(tǒng)調(diào)用實(shí)現(xiàn)的,只是在調(diào)用參數(shù)(flags)上不同,導(dǎo)致創(chuàng)建的執(zhí)行單元具有不一樣的資源共享情況,從而造就了線程和進(jìn)程實(shí)質(zhì)上的差異。

1.1 多線程的特點(diǎn) multi-threaded
從上面的圖中看出,同一個(gè)進(jìn)程中的多個(gè)線程,跟執(zhí)行狀態(tài)相關(guān)的資源都是獨(dú)立的,比如:運(yùn)行棧、優(yōu)先級(jí)、程序計(jì)數(shù)器、信號(hào)掩碼等都是獨(dú)立的,而打開的文件描述符(包含套接字)、地址空間(除了函數(shù)中的自動(dòng)變量屬于棧管理,還有新提出來的線程局部變量,其它基本都是共享的)都是共享的。這里還設(shè)計(jì)到信號(hào)處理句柄、信號(hào)掩碼等,因?yàn)樵诙嗑€程中信號(hào)的問題比較的復(fù)雜,后面單獨(dú)列出來解釋。
共享相同的地址空間、文件描述符給程序的開發(fā)帶來了極大的便利,創(chuàng)建多線程的開銷要小的多,而且在運(yùn)行中任務(wù)切換損失也很小,很多的緩存都維持有效的,還有比如負(fù)責(zé)套接字listen的線程和工作線程之間可以方便的傳遞網(wǎng)絡(luò)連接創(chuàng)建的套接字,生產(chǎn)線程和消費(fèi)線程可以方便的用隊(duì)列進(jìn)行數(shù)據(jù)交換,程序設(shè)計(jì)也可以特化出日志記錄、數(shù)據(jù)落盤等工作線程各司其職。但是天下沒有免費(fèi)的午餐,任何的便利都是需要付出代價(jià)的,多個(gè)執(zhí)行單元可以訪問資源意味著共享資源必須得到保護(hù)和同步,這是多線程程序設(shè)計(jì)不可回避的問題:
(1). 多個(gè)線程可以安全的訪問只讀的資源,但是哪怕只有一個(gè)修改者也是不安全的,額外說一句,我們說的保護(hù)是保護(hù)的資源,而不是行為;
(2). 傳統(tǒng)很多庫(kù)函數(shù)都不是線程安全的,這些函數(shù)當(dāng)初設(shè)計(jì)的時(shí)候沒有考慮到多線程的問題,所以使用了大量的全局變量和靜態(tài)局部變量,這些函數(shù)是不可重入的。所以在你調(diào)用庫(kù)函數(shù)、鏈接別人庫(kù)的時(shí)候,一定要看看有沒有”_r”后綴的版本;
(3). 還要就是之前不斷被提到的內(nèi)存模型,因?yàn)橥瑐€(gè)進(jìn)程中的多個(gè)線程可能會(huì)并行的執(zhí)行,這時(shí)候如果在線程之間有高速度的數(shù)據(jù)同步需求的時(shí)候,必須讓資源的更新能夠及時(shí)地被別的線程感知到;
(4). 多線程程序正因?yàn)榫€程之間共享的資源太多,所以如果一個(gè)線程出現(xiàn)嚴(yán)重的問題,其余的線程也會(huì)被殺死。遙想當(dāng)年在TP-LINK的時(shí)候,所有的服務(wù)功能都以線程的形式被包裹在一個(gè)用戶進(jìn)程中,某個(gè)模塊出現(xiàn)問題都可能導(dǎo)致上不了網(wǎng)需要重啟,所以現(xiàn)在看來穩(wěn)定運(yùn)行的TP-LINK路由器不得不說是一個(gè)奇跡~
1.2 多進(jìn)程的特點(diǎn) multi-process
多進(jìn)程程序之間保證了資源的高度隔離,只在創(chuàng)建出來的父子進(jìn)程之間有少量的聯(lián)系,進(jìn)程組、回話等就不在此討論了。
這個(gè)時(shí)候需要共享的資源必須顯式共享,雖然操作系統(tǒng)優(yōu)化機(jī)制可以讓他們的只讀數(shù)據(jù)(比如執(zhí)行代碼)物理上共享,進(jìn)程間的資源共享或者通過關(guān)聯(lián)到文件系統(tǒng)的某個(gè)路徑或者文件,或者通過全局字符串名字方式,通過以某個(gè)進(jìn)程首先創(chuàng)建資源,其他進(jìn)程打開資源的方式共享。由于歷史原因,Linux進(jìn)程間通信通常包含SYS V和Posix兩套接口,其種類和功能大同小異,但是個(gè)人的實(shí)際感受Posix的操作接口要更加的好用一些。