前言:看了操作系統(tǒng)并發(fā)編程的基礎,做個筆記并用 C 實現常見的一種并發(fā)編程的模型——消費者馍佑、生產者模型
進程之間的關系
第一種是競爭關系
系統(tǒng)中的多個進程之間彼此無關,它們并不知道其他進程的存在茵臭,并且也不受其他進程執(zhí)行的影響舅世。因而, 必然要出現多個進程競爭資源的問題社证。當多個進程競爭共享硬設備评凝、存儲器奕短、處理器 和文件等資源時匀钧,操作系統(tǒng)必須協(xié)調好進程對資源的爭用。
進程的互斥(mutual exclusion )是解決進程間競爭關系(間接制約關系)的手段日杈。 進程互斥指若干個進程要使用同一共享資源時佑刷,任何時刻最多允許一個進程去使用,其他要使用該資源的進程必須等待涨冀,直到占有資源的進程釋放該資源麦萤。
第二種是協(xié)作關系
某些進程為完成同一任務需要分工協(xié)作,由于合作的每一個進程都是獨立地以不可預知的速度推進翅帜,這就需要相互協(xié)作的進程在某些協(xié)調點上協(xié) 調各自的工作命满。
進程的同步(Synchronization)是解決進程間協(xié)作關系(直接制約關系)的手段。進程同步指兩個以上進程基于某個條件來協(xié)調它們的活動狭莱。一個進程的執(zhí)行依賴于另一個協(xié)作進程的消息或信號腋妙,當一個進程沒有得到來自于另一個進程的消息或信號時則需等待,直到消息或信號到達才被喚醒骤素。
不難看出,進程互斥關系是一種特殊的進程同步關系痕檬,即逐次使用互斥共享資源送浊,也是對進程使用資源次序上的一種協(xié)調袭景。
如何實現互斥
無論是競爭還是協(xié)作,都必須實現互斥耸棒。
而實現互斥的關鍵在于:進程 A 正在使用的臨界資源与殃,哪怕在使用的過程中被中斷了,其他進程的臨界區(qū)都不能動進程 A 的資源米奸。
硬件方式實現互斥
用硬件的方式增加一條匯編指令衣屏,比如 TestAndSet
偽代碼如下
// 主程序
global lock = false
repeate {nothing} until TestAndSet(&lock)
// 臨界區(qū)代碼
// do someting
// 臨界區(qū)結束
lock = false
這里的 TestAndSet 使用匯編指令實現的,也就是他不能被中斷膨疏。lock 是全局變量钻弄,所有的進程都能訪問到
實現的功能就是:檢查 lock 是否是 false佃却,如果是 false 就把 lock 設為 true。如果 lock 是 false窘俺。就不會執(zhí)行repeate {nothing}
饲帅。
有的進程執(zhí)行的時候,lock 是 true。就會一直執(zhí)行 repeate {nothing}
灶泵,直到 CPU 時間片結束育八,結束調用。這樣就實現了互斥赦邻。
但是髓棋,這樣有一個很大的缺點就是忙等,我們不想要忙等惶洲,我們想要 lock = true
的時候進程被阻塞按声,然后等待調用
用信號量(semaphore)實現互斥 重點恬吕!
semaphore 最重要的是兩個函數:
wait(semaphore* s)
signal(semaphore* s)
這是系統(tǒng)給的原語签则。原語和其他函數最大的區(qū)別就是:它雖然也是一系列指令構成,但是在執(zhí)行的過程中不可中斷
wait 和 signal 的偽代碼如下:
wait(semaphore* s) {
if(s.count-- < 0) {
// 阻塞
}
}
signal(semaphore* s) {
if(s.count++ <= 0) {
// 調用某個被阻塞的進程
call();
}
}
那怎么用這兩個函數實現互斥呢铐料?我們在訪問臨界資源的時候會先執(zhí)行 wait 函數如果 (s.count-- < 0)渐裂。則被阻塞,而不是忙等余赢。否則就執(zhí)行臨界區(qū)代碼(資源夠用芯义,不需要阻塞)哈垢。
而執(zhí)行完臨界區(qū)以后妻柒,會執(zhí)行 signal 函數。如果 (s.count++ <= 0) 耘分。則去喚醒一個被阻塞的函數举塔,否則啥也不做,僅僅加一(沒有被阻塞的進程)求泰。
通過一個實例來理解這一個過程
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
sem_t s;
int main(int argc, char const *argv[])
{
int value = 0;
sem_init(&s, 0, 10);
sem_getvalue(&s, &value);
printf("initialization s.count = %d\n", value);
// wait
sem_wait(&s);
sem_getvalue(&s, &value);
printf("after wait s.count = %d\n", value);
// signal
sem_post(&s);
sem_getvalue(&s, &value);
printf("after signal s.count = %d\n", value);
sem_destroy(&s);
return 0;
}
輸出:
initialization s.count = 10
after wait s.count = 9
after signal s.count = 10
C 實例
我們通過兩個經典模型央渣,來深入理解進程并發(fā)編程
消費者、生產者模型
該問題描述了共享固定大小緩沖區(qū)的兩個進程——即所謂的“生產者”和“消費者”——在實際運行時會發(fā)生的問題渴频。生產者的主要作用是生成一定量的數據放到緩沖區(qū)中芽丹,然后重復此過程。與此同時卜朗,消費者也在緩沖區(qū)消耗這些數據拔第。該問題的關鍵就是要保證生產者不會在緩沖區(qū)滿時加入數據,消費者也不會在緩沖區(qū)中空時消耗數據场钉。
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <unistd.h>
#define SIZE 1 /*buffer size*/
sem_t empty;
sem_t full;
sem_t mutex;
int buffer[SIZE] = {0};
void producer(int idx, int value) {
sem_wait(&full);
sem_wait(&mutex);
buffer[idx] = value;
sem_post(&mutex);
sem_post(&empty);
}
void consumer(int idx) {
sem_wait(&empty);
sem_wait(&mutex);
printf("從 buffer[%d] 取出 %d \n", idx, buffer[idx]);
buffer[idx] = -1;
sem_post(&mutex);
sem_post(&full);
}
int main(int argc, char const *argv[]) {
int value;
// 多個生產者和消費者蚊俺,只有一個進程能夠擁有 buffer,這是所有進程的互斥
sem_init(&mutex, 0, 1);
// 生產者在滿的時候不能生產
sem_init(&full, 0, SIZE);
// 消費者不能在空的時候消費
sem_init(&empty, 0, 0);
// 假設有四個進程
for(int i = 0; i < 4; i++) {
int p = fork();
if(p == 0) {
switch (i)
{
case 0:
producer(0, 10);
break;
case 1:
producer(0, 100);
break;
case 2:
consumer(0);
break;
case 3:
consumer(0);
break;
default :
// producer(0, 10);
break;
}
} else {
printf("我是主進程:%d \n", p);
}
}
sem_destroy(&mutex);
sem_destroy(&empty);
sem_destroy(&full);
return 0;
}