在
OS
中引入進程後,系統中的多道程序能夠併發執行,但系統卻變得更加複雜,爲使進程有序運行,引入了同步機制。在進程之間傳送大量數據,也須要利用進程通訊工具。這篇文章總結了進程的幾種同步方式和進程之間的通訊方式。數組
爲避免競爭條件,操做系統須要利用同步機制在併發執行時,保證對臨界區的互斥訪問。進程同步的解決方案主要有:信號量和管程。bash
對於同步機制,須要遵循如下四個規則:數據結構
CPU
,如轉換到阻塞態;信號量機制(semaphore
)是一種協調共享資源訪問的方法。信號量由一個變量 semaphore
和兩個原子操做組成,信號量只能經過 P
和 V
操做來完成,並且 P
和 V
操做都是原子操做。併發
將信號量表示以下:工具
typedef struct {
int value;
struct process_control_block *list;
} semaphore;
複製代碼
相應的 P(wait)
操做和 V(signal)
操做以下實現:ui
wait(semaphore *S) {
S->value--;
if(S->value < 0) {
block(S->list);
}
}
signal(semaphore *S) {
S->value++;
if(S->value <= 0) {
wakeup(S->list);
}
}
複製代碼
信號量可分爲兩類:互斥信號量,信號量大小爲爲 0
或 1
,用來實現進程的互斥訪問;資源信號量,信號量大小爲資源數,用來表示系統資源數目。spa
資源信號量操作系統
表明資源信號量時,S->value
初值表示系統資源的數目,P
操做意味着進程請求一個資源,因而系統中可分配的資源數減一,若是 S->value < 0
,表示該類資源已分配完畢,所以阻塞該進程,並插入信號量鏈表 S->list
中。小於 0
時,S->value
的絕對值表示該信號量鏈表中阻塞的進程數。線程
V
操做表示進程釋放一個資源,因而系統中可分配的資源數加一,若是增長一後仍然 S->value <= 0
,表示該信號量鏈表中仍然有阻塞的進程,所以調用 wakeup
,將 S->list
中的第一個進程喚醒。code
互斥信號量
表明互斥信號量時,S->value
初值爲 1
,表示只容許一個進程訪問該資源。
利用信號量實現兩個進程互斥描述以下:
semaphore mutex = 1;
P() {
wait(mutex);
臨界區;
signal(mutex);
}
複製代碼
當 mutex = 1
時,表示兩個進程都沒有進入臨界區,當 mutex = 0
時,表示一個進程進入臨界區運行;當 mutex = -1
時,表示一個進程進入臨界區運行,另外一個進程被阻塞在信號量隊列中。
管程採用面向對象思想,將表示共享資源的數據結構及相關的操做,包括同步機制,都集中並封裝到一塊兒。全部進程都只能經過管程間接訪問臨界資源,而管程只容許一個進程進入並執行操做,從而實現進程互斥。
Monitor monitor_name {
share variable declarations;
condition declarations;
public:
void P1(···) {
···
}
{
initialization code;
}
}
複製代碼
管程中設置了多個條件變量,表示多個進程被阻塞或掛起的條件,條件變量的形式爲 condition x, y;
,它也是一種抽象數據類型,每一個變量保存了一條鏈表,記錄因該條件而阻塞的進程,與條件變量相關的兩個操做:condition.cwait
和 condition.csignal
。
condition.cwait
:正在調用管程的進程因 condition
條件須要被阻塞,則調用 condition.cwait
將本身插入到 condition
的等待隊列中,並釋放管程。此時其餘進程能夠使用該管程。condition.csignal
:正在調用管程的進程發現 condition
條件發生變化,則調用 condition.csignal
喚醒一個因 condition
條件而阻塞的進程。若是沒有阻塞的進程,則不產生任何結果。生產者-消費者問題描述的是:生產者和消費者兩個線程共享一個公共的固定大小的緩衝區,生產者在生成產品後將產品放入緩衝區;而消費者從緩衝區取出產品進行處理。
它須要保證如下三個問題:
利用信號量解決
用信號量解決生產者-消費者問題,使用了三個信號量:
mutex
:用來保證生產者和消費者對緩衝區的互斥訪問;full
:記錄已填充的緩衝槽數目;empty
:記錄空的緩衝槽數目。#define N 10
int in = 0, out = 0;
item buffer[N];
semaphere mutex = 1, full = 0, empty = N;
void producer(void) {
while(TRUE) {
item nextp = produce_item();
wait(empty);
wait(mutex);
buffer[in] = nextp;
in = (in + 1) % N;
signal(mutex);
signal(full);
}
}
void consumer(void) {
while(TRUE) {
wait(full);
wait(mutex);
item nextc = buffer[out];
out = (out + 1) % N;
signal(mutex);
signal(empty);
consume_item(nextc);
}
}
複製代碼
須要注意的是進程中的多個 wait
操做順序不能顛倒,不然可能形成死鎖。例如在生產者中,當系統中沒有空的緩衝槽時,生產者進程的 wait(mutex)
獲取了緩衝區的訪問權,但 wait(empty)
會阻塞,這樣消費者也沒法執行。
利用管程解決
利用管程解決時,須要爲它們創建一個管程,其中 count
表示緩衝區中已有的產品數目,條件變量 full
和 empty
有 cwait
和 csignal
兩個操做,另外還包括兩個過程:
put(x)
:生產者將本身生產的產品放入到緩衝區中,而若是 count >= N
,表示緩衝區已滿,生產者須要等待;get(x)
:消費者從緩衝區中取出一個產品,若是 count <= 0
,表示緩衝區爲空,消費者應該等待;Monitor producerconsumer {
item buffer[N];
int in, out;
condition full, emtpy;
int count;
public:
void put(item x) {
if(count >= N) {
cwait(full);
}
buffer[in] = x;
in = (in + 1) % N;
count++;
csignal(emtpy);
}
item get() {
if(count <= 0) {
cwait(emtpy);
}
x = buffer[out];
out = (out + 1) % N;
count--;
csignal(full);
}
{ in = 0; out = 0; count = 0; }
}
複製代碼
因而生產者和消費者可描述爲:
void producer() {
while(TRUE) {
item nextp = produce_item();
producerconsumer.put(nextp);
}
}
void consumer() {
while(TRUE) {
item nextc = producerconsumer.get();
consume_item(nextc);
}
}
複製代碼
哲學家就餐問題描述的是:有五個哲學家共用一個圓桌,分別坐在周圍的五張椅子上,在圓桌上有五個碗和五隻筷子,他們交替地進行思考和進餐。哲學家在平時進行思考,在飢餓時試圖獲取左右兩隻筷子,拿到兩隻筷子才能進餐,進餐完後放下筷子繼續思考。
爲實現筷子的互斥使用,能夠用一個信號量表示一隻筷子,五個信號量構成信號量數組,也都被初始化爲 1
。
semaphore chopstick[5] = {1, 1, 1, 1, 1};
複製代碼
第 i
位哲學家的活動可描述爲:
void philosopher(int i) {
while(TRUE) {
wait(chopstick[i]);
wait(chopstick[(i + 1) % 5]);
// eat
signal(chopstick[i]);
signal(chopstick[(i + 1) % 5]);
// think
}
}
複製代碼
上述解法中,若是五位哲學家同時飢餓而都拿起左邊的筷子,再試圖去拿右邊的筷子時,會出現無限期等待而引發死鎖。
讀者-寫者問題描繪的是:一個文件能夠被多個進程共享,容許多個 Reader
進程同時讀這個文件,但不容許 Wirter
進程和其餘 Reader
進程或 Writer
進程同時訪問這個文件。因此讀者-寫者須要保證一個 Writer
進程必須與其餘進程互斥地訪問共享對象。
解決這個問題須要設置兩個互斥信號量和一個整形變量:
wmutext
:實現 Reader
進程和 Writer
進程在讀或寫時的互斥;readcount
:正在讀的進程數目;rmutext
:實現多個 Reader
進程對 readcount
變量的互斥訪問;semaphore rmutex = 1, wmutex = 1;
int readcount = 0;
void Reader() {
while(TRUE) {
wait(rmutex);
if(readcount == 0) {
wait(wmutex);
}
readcount++;
signal(rmutex);
// perform read opertaion
wait(rmutex);
readcount--;
if(readcount == 0) {
signal(wmutex);
}
signal(rmutex);
}
}
void Writer() {
while(TRUE) {
wait(wmutex);
// perform wirte opertaion
signal(wmutex);
}
}
複製代碼
只要有一個 Reader
進程在讀,便不容許 Writer
進程去寫。因此,僅當 readcount = 0
,表示沒有 Reader
進程在讀時,Reader
進程才須要執行 wait(wmutex)
操做,而 readcount != 0
時,表示有其餘 Reader
進程在讀,也就確定沒有 Writer
在寫。同理,僅當 readcount = 0
時,才執行 signal(wmutex)
相似。
進程通訊是指進程之間的信息交換。在進程間要傳送大量數據時,應利用高級通訊方法。
在共享內存系統中,多個通訊的進程共享某些數據結構或存儲區,進程之間可以經過這些空間進行通訊。
可分爲兩種類型:
管道(Pipe
)是指用於鏈接一個讀進程和一個寫進程以實現進程間通訊的一個共享文件。發送進程以字符形式將數據送入管道,而接收進程則從管道中接收數據。
管道機制提供了三方面的協調能力:
消息傳遞機制中,進程以格式化的消息爲單位,將通訊的數據封裝在消息中,並利用操做系統提供的原語,在進程之間進行消息傳遞,完成進程間數據交換。
按照實現方式,可分爲兩類: