進程同步
臨界資源
一次僅允許一個進程使用的資源稱爲臨界資源。
臨界區
對臨界資源進行訪問的那段代碼稱爲臨界區。
同步和互斥
同步: 多個進程因爲合作產生的直接制約關係,使得進程有一定的先後執行關係。
互斥: 多個進程在同一時刻只有一個進程能進入臨界區。
信號量
進程之間經常會存在互斥和同步兩種關係.爲了有效的處理這兩種情況,提出了信號量 (semaphore)和PV操作。
信號量是一個整形變量,可以對其執行PV操作。
- P操作:也稱爲down()操作/wait()操作,使S=S-1,若S<0,進程暫停執行,放入信號量等待隊列
- V操作:也稱up()/signal()操作,使S=S+1,若S<=0,喚醒等待隊列中的一個進程。
PV操作被設計成原語,不可分割。
互斥量
如果信號量的取值只能爲0或1,那麼就成爲了互斥量。
- 0表示臨界區加鎖
- 1表示臨界區解鎖
經典進程同步問題
生產者消費者問題
問題描述
使用一個緩衝區來存放物品,只有緩衝區沒有滿,生產者纔可以放入物品;只有緩衝區不爲空,消費者纔可以拿走物品。
解釋
緩衝區處於臨界資源,因此,需要一個互斥量mutex來控制緩衝區的互斥訪問。
爲了同步生產者和消費者的行爲,需要記錄緩衝區中物品的數量。數量可以用信號量來統計,需要兩個信號量:empty記錄空緩衝區的數量,full記錄滿緩衝區的數量。
empty信號量是在生產者進程中使用,當empty不爲零時,生產者才能放入物品;
full信號量是在消費者進程中使用的,方full信號量不爲0時,消費者纔可以取走物品。
注意
不能對緩衝區先進行加鎖再測試信號量。即不能先P(mutex)再P(empty)。否則可能會出現如下情況。
生產者對緩衝區加鎖,執行P(mutex)在執行P(empty)操作,發現empty=0,此時生產者睡眠。
消費者不能進入臨界區,因爲生產者對緩衝區加鎖了,消費者就無法執行V(empty)操作,empty永遠都是0,導致生產者永遠等待,不釋放鎖,消費者也因此永遠等待。
#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void producer() {
while(TRUE) {
int item = produce_item();
down(&empty);
down(&mutex);
insert_item(item);
up(&mutex);
up(&full);
}
}
void consumer() {
while(TRUE) {
down(&full);
down(&mutex);
int item = remove_item();
consume_item(item);
up(&mutex);
up(&empty);
}
}
讀者寫者問題
描述
允許多個進程同時對數據進行讀操作,但是不允許讀和寫以及寫和寫同時發生。
解決
- 一個整形變量count記錄在對數據進行讀操作的進程數量
- 一個互斥量count_mutex用於對count加鎖
- 一個互斥量data_mutex用於對讀寫的數據進行加鎖
typedef int semaphore;
semaphore count_mutex = 1;
semaphore data_mutex = 1;
int count = 0;
void reader() {
while(TRUE) {
// ---------------------------
down(&count_mutex);
count++;
if(count == 1) down(&data_mutex); // 第一個讀者需要對數據進行加鎖,防止寫進程訪問
up(&count_mutex);
// ---------------------------
read();
down(&count_mutex);
count--;
if(count == 0) up(&data_mutex);
up(&count_mutex);
// ---------------------------
}
}
void writer() {
while(TRUE) {
down(&data_mutex);
write();
up(&data_mutex);
}
}
進程通信
- 進程同步:控制多個進程按一定的順序執行
- 進程通信:進程間傳輸信息
匿名管道通信
管道是通過調用 pipe 函數創建的,pipefd[0] 用於讀,pipefd[1] 用於寫。
#include <unistd.h>
int pipe(int pipefd[2]);
調用pipe函數,會在內核中開闢出一塊緩衝區用來進行進程間通信,這塊緩衝區稱爲管道,它有一個讀端和一個寫端。
可以通過read(pipefd [0]);或者write(pipefd [1]) 操作管道
通信步驟
- 父進程創建管道,pipefd[]指向管道的兩端
- 利用fork函數創建出子進程,子進程的pipefd[]指向同一管道
- 父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程可以往管道中進行寫操作,子進程可以從管道中讀,從而實現了通過管道的進程間通信。
特點
- 只能在父子進程或者兄弟進程中使用
- 是一種半雙工通信,單向交替傳輸
命名管道通信(FIFO)
匿名管道,由於沒有名字,只能用於親緣關係的進程間通信。爲了克服這個缺點,提出了命名管道(FIFO)。
命名管道不同於匿名管道之處在於它提供了一個路徑名與之關聯,以命名管道的文件形式存在於文件系統中,這樣,即使與命名管道的創建進程不存在親緣關係的進程,只要可以訪問該路徑,就能夠彼此通過命名管道相互通信,因此,通過命名管道不相關的進程也能交換數據。
命名管道遵循先進先出原則,對匿名管道及命名管道的讀總是從開始處返回數據,對它們的寫則把數據添加到末尾。命名管道的名字存在於文件系統中,內容存放在內存中。
信號
信號是Linux系統中用於進程間互相通信或者操作的一種機制,信號可以在任何時候發給某一進程,而無需知道該進程的狀態。
信號量
信號量本質上是一個計數器,用於多進程對共享數據對象的讀取,它和管道有所不同,它不以傳送數據爲主要目的,它主要是用來保護共享資源,使得資源在一個時刻只有一個進程獨享。
共享存儲
- 允許多個進程共享一個給定的存儲區。是最快的一種 IPC,因爲數據不需要在進程之間複製。
- 爲了在多個進程間交換信息,內核專門留出了一塊內存區,可以由需要訪問的進程將其映射到自己的私有地址空間。進程就可以直接讀寫這一塊內存而不需要進行數據的拷貝,從而大大提高效率。
- 由於多個進程共享一段內存,因此需要依靠如信號量來達到進程間的同步及互斥。
套接字
藉助套接字,要進行通信的進程,可以在本地單機上進行,也可以跨網絡進行。
它可以讓不在同一臺計算機但通過網絡連接計算機上的進程進行通信。
消息隊列
- 消息隊列是存放在內核中的消息鏈表,每個消息隊列由消息隊列標識符表示。
- 消息隊列允許一個或多個進程向它寫入與讀取消息。
- 消息隊列的通信數據都是先進先出的原則。
- 讀進程可以根據消息類型有選擇地接收消息。