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