現(xiàn)代操作系統(tǒng)提供了基于三種基本的構(gòu)造并發(fā)程序的方法。分別為:進(jìn)程、I/O 多路復(fù)用和線程。
基于進(jìn)程的并發(fā)編程方法很簡單,使用我們很熟悉的fork、exec、waitpid等函數(shù)就可以了。比如構(gòu)造一個(gè)并發(fā)服務(wù)器的方法就是,在父進(jìn)程中接受客戶端的請(qǐng)求,然后創(chuàng)建一個(gè)新的子進(jìn)程來為每個(gè)客戶端提供服務(wù)。
因?yàn)檫M(jìn)程有獨(dú)立的地址空間,所以不會(huì)出現(xiàn)進(jìn)程不小心覆蓋另一個(gè)進(jìn)程虛擬內(nèi)存的情況,這是一個(gè)顯著的優(yōu)點(diǎn)。但獨(dú)立地址空間也讓不同進(jìn)程之間共享信息變得更加困難。
考慮下面一種場景,服務(wù)器也能對(duì)用戶從標(biāo)準(zhǔn)輸入鍵入的交互命令做出響應(yīng),那服務(wù)器就必須響應(yīng)兩個(gè)相互獨(dú)立的 I/O 事件:1)客戶端連接請(qǐng)求,2)用戶鍵入命令行。我們無法選擇應(yīng)該等待哪個(gè)事件,因?yàn)榈却渲幸粋€(gè)就不能響應(yīng)另一個(gè)事件,所以就有了 I/O 多路技術(shù)。
其基本思路就是使用select函數(shù),要求內(nèi)核掛起進(jìn)程,只有在一個(gè)或多個(gè) I/O 事件發(fā)生后,才將控制返回給應(yīng)用程序。
該技術(shù)可以用作并發(fā)事件驅(qū)動(dòng)程序的基礎(chǔ),在事件驅(qū)動(dòng)程序中某些事件會(huì)導(dǎo)致流向前推進(jìn)(比如 web 前端程序),一般思路是將邏輯流轉(zhuǎn)換為狀態(tài)機(jī)(去查編譯原理),某些事件會(huì)導(dǎo)致從一個(gè)狀態(tài)轉(zhuǎn)移到另一個(gè)狀態(tài)。
如果使用進(jìn)程并發(fā)實(shí)現(xiàn)上面的場景,那可能就需要兩個(gè)進(jìn)程分別監(jiān)聽兩個(gè)事件,而使用 I/O 多路復(fù)用技術(shù)的程序時(shí)運(yùn)行在單一進(jìn)程上下文中,因此每個(gè)邏輯流都能訪問全部該進(jìn)程全部的地址空間,也就是說共享數(shù)據(jù)變得很容易,但是事件驅(qū)動(dòng)要比基于進(jìn)程更加復(fù)雜,而且隨著并發(fā)粒度的減小,復(fù)雜性還會(huì)上升。另外如果某個(gè)邏輯流正忙于讀一個(gè)文本行,那么其它邏輯流就不會(huì)有進(jìn)展,這防不住惡意客戶端的攻擊。
線程同時(shí)結(jié)合了前面兩種方法。線程是運(yùn)行在進(jìn)程上下文的邏輯流,它有自己的棧、棧指針、程序計(jì)數(shù)器等,但是共享這個(gè)進(jìn)程虛擬空間的所有內(nèi)容,包括堆、代碼、數(shù)據(jù)、共享庫及打開文件。