概述
信號量是一種用於提供不同進程間或一個給定進程的不同線程間同步手段的原語。下圖表示的是由兩個進程使用的一個二值信號量
一個進程可以在信號量執行三種操作:
1. 創建(create)一個信號量,這要求調用者指定初始值,對於二值信號量來說,它通常是1,
2. 等待(wait)一個信號量,該操作會測試這個信號量的值,如果其值小於或等於0,那就等待or阻塞,一旦其值變爲大於1就將它減1,過程如以下僞代碼
3. 掛出(post)一個信號量,該操作將信號量的值加1
當然,信號量也可以實現互斥的目的,下圖就是一個例子
其中信號量與其他同步操作(互斥鎖,條件變量,讀寫鎖)的區別有
1.互斥鎖必須總是由鎖住它的線程解鎖,信號量的掛出卻不必由執行過它的等待操作的同一線程執行(一個線程可以等待某個給定信號量,而另一個線程可以掛出該信號量)
2. 任何一個線程都可以掛起一個信號,即使當時沒有線程在等待該信號值變爲正數也沒有關係。但是,如果某個新城調用了pthread_cond_signal,不過當時沒有任何線程阻塞在pthread_cond_wait調用中,那麼發往相應的條件變量的信號將丟失
3. 能夠從信號處理程序中安全調用的唯一函數是sem_post
解決一個生產者與一個消費者問題,如下圖模型
現在把共享緩衝區當作一個環繞緩衝區,生產者填寫最後一項後,回過頭來填寫第一項,消費者也可以這樣做。此時,我們必須用代碼來爲池以下三個條件:
當緩衝區爲空時,消費者不能試圖從其中去除一個條目
當緩衝區填滿時,生產者不能試圖往其中放置一個條目
共享變量可能描述緩衝區的當前狀態,因此生產者與消費者的所有緩衝區操縱都必須保護起來(互斥操作),以避免竟爭狀態
開始時,我們需要初始化一個緩衝區,和兩個計數信號量(表示緩衝區滿,和空的信號量),具體如下圖
初始化後的緩衝區和兩個計數信號量
具體代碼實現
#include <stdio.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <sys/types.h>
#include <pthread.h>
#include <fcntl.h>
#define NBUFF 10
char SEM_MUTEX[] = "mutex";
char SEM_NEMPTY[] = "nempty";
char SEM_NSTORED[] = "nstored";
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int nitems;
struct
{
int buff[NBUFF];
sem_t mutex, nempty, nstored;
}shared;
void *producer(void *);
void *consumer(void *);
int main(int argc, char *argv[])
{
pthread_t tid_producer, tid_consumer;
if(argc!=2)
{
printf("useage: procon <#items>");
exit(1);
}
nitems = atoi(argv[1]);
//create three non-name semaphores
sem_init(&shared.mutex, 0, 1);
sem_init(&shared.nempty, 0, NBUFF);
sem_init(&shared.nstored, 0, 0);
//create one producer thread and consumer thread
pthread_create(&tid_producer, NULL, producer, NULL);
pthread_create(&tid_consumer, NULL, consumer, NULL);
pthread_setconcurrency(2);
//wait for two threads
pthread_join(tid_producer, NULL);
pthread_join(tid_consumer, NULL);
//destory three semaphores
sem_destroy(&shared.mutex);
sem_destroy(&shared.nempty);
sem_destroy(&shared.nstored);
return 0;
}
void *producer(void *arg)
{
int i=0;
for(i=0; i<nitems; i++)
{
sem_wait(&shared.nempty);
sem_wait(&shared.mutex);
shared.buff[i % NBUFF] = i+1;
sem_post(&shared.mutex);
sem_post(&shared.nstored);
}
return NULL;
}
void *consumer(void *arg)
{
int i=0;
for(i=0; i<nitems; i++)
{
//here, if we exchange first two sem_wait's position, then it will cause dead lock
sem_wait(&shared.nstored);
sem_wait(&shared.mutex);
if(shared.buff[i % NBUFF] == i+1)
printf("buff[%d] = %d\n", i, shared.buff[i % NBUFF]);
sem_post(&shared.mutex);
sem_post(&shared.nempty);
}
return NULL;
}
注意,在消費者函數中,如果交換兩個sem_wait的順序,即先sem_wait(&shared.mutex),再sem_wait(&shared.nstored),則會到這死鎖。原因:假設,生產者線程先工作,它使shared.nempty從NBUFF減到1,shared.nstored從0增至NBUFF。接下來消費者線程將shared.nstored從NBUFF減至0,使shared.nempty從0增至NBUFF後,接下來調用sem_wait(&shared.mutex),阻塞,此時生產者線程也調用sem_wait(&shared.mutex),阻塞,這時候導致死鎖現象。因爲此時生產者在等待mutex信號,但是消費者卻持有該信號量並等待shared.nstored信號量。然而生產者只有獲取了mutex信號才能掛出nstored信號量。