線程同步之信號量Semaphore

信號量是內核對象,它允許多個線程在同一個時刻訪問同一個共享資源,但是需要限制在同一時刻訪問此共享資源的最大線程數量。在創建信號量時,要指定允許的最大資源計數和當前可用的資源數。一般將當前可用資源數設置爲最大資源數,每增加一個線程對共享資源的訪問,當前可用資源數減1。當可用資源計數減小到0時,則說明當前佔用資源的線程數達到了所允許的最大數目,其他線程無法再進入,必須等待(阻塞)。佔用資源的線程在處理完成後,會釋放佔用的資源,此時可用資源計數加1,這時等待的線程隊列中,會有其中一個線程被喚醒並獲取資源。

信號量有兩種類型:二進制信號量計數信號量
二進制信號量,只有0和1兩種取值。一般用於保護一段代碼使其每次只被一個線程執行,這種信號量可代替互斥鎖來實現對資源的互斥訪問。
計數信號量,可以有更大的取值範圍,允許有限數目的線程共同訪問某個共享資源。

常用的信號量操作有如下一些函數:

信號量的創建

 #include <semaphore.h>

 int sem_init(sem_t *sem, int pshared, unsigned int value);

 sem即要被創建並初始化的信號量指針;
 value用來指定信號量的初始值,也就是共享資源同時可被訪問的最大線程數;

第二個參數pshared需要注意,該參數設置爲0時,表明該信號量是在進程內使用,即實現線程之間的同步,pshared需要定義在所有線程都可見的位置,比如定義成全局變量,或者動態分配在堆上的變量。該參數設置爲非零值時,用於進程之間的同步,這時信號量需要定義在共享內存區域中,任何可以訪問該共享內存區域的進程都可以通過sem_post()和sem_wait()操作該信號量。

本篇我們只討論進程內的同步。

信號量的控制

#include <semaphore.h>

int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

sem_post()會使信號量的值遞增,如果信號量的值大於0,那麼另一個阻塞在sem_wait()上等待該信號的線程就會被喚醒,並獲取共享資源;

sem_wait()會使信號量的值遞減。如果信號量的值大於0,那麼執行遞減操作並立即返回;若信號量的當前值爲0,那麼該調用將會被阻塞,直到信號量的值變爲正值,或者被某個信號處理函數中斷該調用。

sem_trywait(),與sem_wait()作用類似,不同的是當信號量的值爲0而無法執行遞減操作時,返回一個錯誤碼(EAGAIN),而不是阻塞宿主線程的執行。

sem_timedwait(),與sem_wait()作用類似,不同的是設置了一個阻塞的時長abs_timeout,當阻塞時間超過該時長時,返回一個ETIMEDOUT錯誤。

信號量的銷燬

#include <semaphore.h>

int sem_destroy(sem_t *sem);

sem_destroy()用來銷燬一個未命名的信號量,信號量地址由參數sem指定。只有由sem_init()函數初始化的信號量才能調用sem_destroy()銷燬。信號量在銷燬前要確保沒有其他線程或進程在佔用它。一個信號量被銷燬後不可再被使用,否則會產生未知結果,被銷燬的信號量,可通過sem_init()重新初始化後再被使用。

下面我們通過一個例子來理解信號量的工作機制。

假設一個醫院的某科室有三個醫生,每個醫生每次接診一個病人,那麼該科室最多可接診的病人數量爲3。假如一段時間內,該科室僅提供20個掛號名額,也就是說有20人就診,那麼我們如何使用信號量的機制來模擬該科室的就診情況呢?

/* 醫生和病人的例子:3個醫生,20個病人,每次每個醫生服務一個病人,也就是說,最多有3個病人可同時就診 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>

/* 信號量定義爲全局變量 */
sem_t g_sem;

/* 定義一定時間段內,接診的病人總數量 */
const int PATIENT_NUM = 20;

/* 每個線程模擬一個病人的就診 */
void *patient_service(void *arg)
{
	int patient_id = *((int *)arg);  //輸入參數爲病患ID
	
	if(sem_wait(&g_sem) == 0)   //病人等待服務資源,即醫生空閒
	{
		printf("Patient %d is seeing the doctor. \n", patient_id);
		sleep(2);   //每個病人的就診時間爲2秒
		sem_post(&g_sem);   //當前病人服務完成,釋放一個醫生資源
	}
	
	return NULL;
}


int main()
{
	/* 初始化信號量,指定爲線程之間的共享,信號量值爲3 */
	sem_init(&g_sem, 0, 3);
	
	pthread_t patient_thread[PATIENT_NUM];   //每個病人爲一個線程
	
	for(int i = 0; i < PATIENT_NUM; i++)
	{
		int ret;
		int patientID = i;
		
		/* 爲第i個病人創建服務線程 */
		ret = pthread_create(&patient_thread[i], NULL, patient_service, &patientID);
		if(ret != 0)
		{
			printf("pthread_create error! \n");
			exit(EXIT_FAILURE);
		}
		usleep(50);
		
	}
	
	/* 連接已終止的線程 */
	for(int j = 0; j < PATIENT_NUM; j++)
	{
		pthread_join(patient_thread[j], NULL);
	}
	
	/* 最後釋放信號量 */
	sem_destroy(&g_sem);
	
	return 0;
	
}

執行結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章