第十二章 并發程式設計
并發指邏輯控制流在時間上是重疊的
程序:
- 每個控制流都是一個程序
- 是由核心來排程維護
- 有獨立的虛拟位址空間
-
在與其他流進行通信時,控制流必須使用某種顯式的程序間通信機制
I/O多用複路:
- 在并發程式設計中,應用程式在一個程序的上下文中顯式的排程它們自己的邏輯流
-
所有流都共享同一個位址空間
線程:
- 是運作在單一程序上下文中的邏輯流
- 由核心進行排程
- 是程序和I/O多用複路的混合體
基于程序的并發程式設計
基于程序的并發伺服器
由于伺服器運作時間長,是以要包括一個SIGCHLD程序程式,用來回收僵死子程序的資源
關于程序的優劣
對于父,子程序:共享檔案表,但不共享使用者位址空間
基于I/O多用複路的并發程式設計
基本思路:使用select函數,要求核心挂起程序,隻有在一個或多個I/O事件發生後,才将控制傳回應用的程式
例如:
select函數處理類型為fd_set的集合
被允許對描述符集合做的三件事:
- 配置設定他們
- 将一個此種類型的變量指派給另一個變量
-
用FD_ZERO,FD_SET,FD_CLR和FD_ISSET宏指令來修改和檢查他們
select的兩個輸入:
- 讀集合的描述輸入(fdset)
-
讀集合的基數(n)
副作用:select修改了參數fdset指向的fd_set,指明讀集合中一個稱為準備好集合的子集
利用select實作一個疊代echo伺服器:
一旦select傳回,就用FD_ISSET宏指令來判斷哪個可以進行讀了
基于I/O多用複路的并發事件驅動伺服器
一個狀态機就是一組狀态,輸入事件和轉移,其中轉移就是将狀态和輸入事件映射到狀态
自循環同一個輸入和輸出之間的轉換
- select函數檢測到輸入事件
- add_client函數建立一個新的邏輯流
- check_clients函數通過回送輸入行來執行狀态轉移
伺服器使用I/O多用複,借助select函數檢測輸入事件的發生
clientfd數組表示已連接配接描述符的集合,其中整數-1表示一個可用的槽位
I/O多用複路技術的優劣:
優點:
- 它比基于程序的設計給程式員更多的對程式行為的控制
-
一個基于I/O多用複路用事件驅動伺服器是運作在一個單一程序上下文,并每個邏輯流都能通路該程序的全部位址空間
缺點:
- 編碼複雜
基于線程的并發程式設計
每個線程都有它自己的線程上下文,包括一個唯一的整數線程ID,棧,棧指針,程式計數器,通用目的寄存器和條件碼
線程執行模型
主線程:每個程序開始生命周期都是單一線程
某時刻,主線程建立一個對等線程,從此時開始,兩個線程并發運作
線程和程序的差別:
- 一個線程的上下文比一個程序的上下文小很多
- 線程上下文的切換要比程序上下文的切換快很多
- 程序是按照嚴格的父子層次來組織的,線程則是對等的池
Posix線程
- 建立線程
- 終止線程
- 當頂層的線程例程傳回時,線程會隐式地終止
- 通過調用pthread_exit函數,線程會顯式地終止
- 某個對等線程調用Unix的exit函數,該函數終止程序以及與該程序相關的所有線程
- 另一個對等線程通過目前線程ID作為參數調用pthread_cancle函數來終止目前線程
- 回收已終止線程的資源 pthread_join函數會堵塞,直到線程tid終止,将線程例程傳回的(void)指針指派為pthread_return指向的位置,然後回收*已終止線程占用的所有儲存器資源
-
分離線程
在任意一個時間點上,線程是可結合的或者是分離的
- 初始化線程
多線程程式中的共享變量
通常,線程的寄存器是從不共享的,而虛拟儲存器總是共享的
将變量映射到存儲器
- 全局變量:是定義在函數之外的變量,任何線程都可以引用
- 本地自動變量:是定義在函數内部但是沒有static屬性的變量
- 本地靜态變量:是定義在函數内部并有static屬性的變量
共享變量
若說變量v是共享的,當且僅當它的一個執行個體被一個以上的線程引用
用信号量同步線程
在共享變量的同時引入了同步錯誤的可能性
一般而言,沒有辦法預測作業系統是否能為你的線程選擇一個正确的順序
進度圖
進度圖将n個并發線程的執行模型化為一條n維笛卡爾空間中的軌迹線
圖的原點對應于沒有任何線程完成一條指令的初始狀态
在進度圖中,兩個臨界區的交集形成的狀态空間區域稱為不安全區
信号量
- P(s):如果s是非零的,那麼P将s減1,并且立即傳回。若s為零,則挂起這個線程,直到s非零
- V(s):V操作将s加1
使用信号量來實作互斥
基本思想:将每個共享變量與一個信号量s聯系起來,然後用P(s)和V(s)操作将相應的臨界區包圍起來
利用信号量來排程共享資源
生産者-消費者問題
基于預線程化的并發伺服器
使用線程提高并行性
最簡單的方法:将序列劃分成t個不相交區域,然後給t個不同的線程每個配置設定一個區域
并行程式的加速比:
其他并發問題
線程安全
四個線程不安全函數類:
- 不保護共享變量的函數
- 保持跨越多個調用的狀态的函數
- 傳回指向靜态變量指針的函數
- 調用線程不安全函數的函數
可重入性
線上程化的程式中使用已存在的函數庫
競争
當一個程式的正确性一個線程要在另一個線程到達y點之前到達它的控制流中的x點時,就會發生競争
死鎖
避免死鎖的規則:
互斥鎖加鎖順序規則:如果對于程式中每對互斥鎖(s,t),每個同時占用s和t的線程都按照相同的順序對它們加鎖,那這個程式是無死鎖的