Linux下進程間通信方式——信號量(Semaphore)


1.信號量

信號量本質上是一個計數器(不設置全局變量是因為進程間是相互獨立的,而這不一定能看到,看到也不能保證++引用計數為原子操作),用於多進程對共享數據對象的讀取,它和管道有所不同,它不以傳送數據為主要目的,它主要是用來保護共享資源(信號量也屬於臨界資源),使得資源在一個時刻只有一個進程獨享。

2.信號量的工作原理

由於信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv),他們的行為是這樣的:

(1)P(sv):如果sv的值大於零,就給它減1;如果它的值為零,就掛起該進程的執行

(2)V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就給它加1.

在信號量進行PV操作時都為原子操作(因為它需要保護臨界資源)

注:原子操作:單指令的操作稱為原子的,單條指令的執行是不會被打斷的

3.二元信號量

二元信號量(Binary Semaphore)是最簡單的一種鎖(互斥鎖),它只用兩種狀態:占用與非占用。所以它的引用計數為1。

4.進程如何獲得共享資源

(1)測試控制該資源的信號量

(2)信號量的值為正,進程獲得該資源的使用權,進程將信號量減1,表示它使用了一個資源單位

(3)若此時信號量的值為0,則進程進入掛起狀態(進程狀態改變),直到信號量的值大於0,若進程被喚醒則返回至第一步。

注:信號量通過同步與互斥保證訪問資源的一致性。

5.與信號量相關的函數

所有函數共用頭文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h> 

5.1創建信號量

int semget(key_t key,int nsems,int flags)
                                  //返回:成功返回信號集ID,出錯返回-1

  

(1)第一個參數key是長整型(唯一非零),系統建立IPC通訊 ( 消息隊列、 信號量和 共享內存) 時必須指定一個ID值。通常情況下,該id值通過ftok函數得到,由內核變成標識符,要想讓兩個進程看到同一個信號集,只需設置key值不變就可以。

(2)第二個參數nsem指定信號量集中需要的信號量數目,它的值幾乎總是1。

(3)第三個參數flag是一組標志,當想要當信號量不存在時創建一個新的信號量,可以將flag設置為IPC_CREAT與文件權限做按位或操作。
設置了IPC_CREAT標志后,即使給出的key是一個已有信號量的key,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則可以創建一個新的,唯一的信號量,如果信號量已存在,返回一個錯誤。一般我們會還或上一個文件權限

5.2刪除和初始化信號量

int semctl(int semid, int semnum, int cmd, ...);

如有需要第四個參數一般設置為union semnu arg;定義如下

union semun
{  
    int val;  //使用的值
    struct semid_ds *buf;  //IPC_STAT、IPC_SET 使用的緩存區
    unsigned short *arry;  //GETALL,、SETALL 使用的數組
    struct seminfo *__buf; // IPC_INFO(Linux特有) 使用的緩存區 
};

  

(1)sem_id是由semget返回的信號量標識符

(2)semnum當前信號量集的哪一個信號量

(3)cmd通常是下面兩個值中的其中一個
SETVAL:用來把信號量初始化為一個已知的值。p 這個值通過union semun中的val成員設置,其作用是在信號量第一次使用前對它進行設置。
IPC_RMID:用於刪除一個已經無需繼續使用的信號量標識符,刪除的話就不需要缺省參數,只需要三個參數即可。

5.3改變信號量的值

int semop(int semid, struct sembuf *sops, size_t nops); 

  

(1)nsops:進行操作信號量的個數,即sops結構變量的個數,需大於或等於1。最常見設置此值等於1,只完成對一個信號量的操作

(2)sembuf的定義如下:

struct sembuf{  
    short sem_num;   //除非使用一組信號量,否則它為0  
    short sem_op;   //信號量在一次操作中需要改變的數據,通常是兩個數,                                         
                    //一個是-1,即P(等待)操作,  
                    //一個是+1,即V(發送信號)操作。  
    short sem_flg; //通常為SEM_UNDO,使操作系統跟蹤信號量,  
                  //並在進程沒有釋放該信號量而終止時,操作系統釋放信號量  
};  

  

5.4sembuf中sem_flg的設置問題

通常設置為SEM_UNDO,使操作系統跟蹤信號量, 並在進程沒有釋放該信號量而終止時,操作系統釋放信號量 ,例如在二元信號量中,你不釋放該信號量 而異常退出,就會導致別的進程一直申請不到信號量,而一直處於掛起狀態。

是否設置sem_flg為SEM_UNDO的區別

6.模擬實現信號量實現進程間通信

用一個上課的時候講過的簡單的例子來說明一下:

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <errno.h> 
#define total 20 
sem_t remain, apple, pear, mutex;
static unsigned int vremain = 20, vapple = 0, vpear = 0; 
void *father(void *);
void *mather(void *);
void *son(void *);
void *daughter(void *);
void print_sem(); 
int main() 
{	
	pthread_t fa, ma, so, da;	
	sem_init(&remain, 0, total);//總數初始化為20	
	sem_init(&apple, 0, 0);//盆子中蘋果數, 開始為0	
	sem_init(&pear, 0, 0);//盆子中梨子數, 開始為0	
	sem_init(&mutex, 0, 1);//互斥鎖, 初始為1 	
	pthread_create(&fa, NULL, &father, NULL);	
	pthread_create(&ma, NULL, &mather, NULL);	
	pthread_create(&so, NULL, &son, NULL);	
	pthread_create(&da, NULL, &daughter, NULL); 	
	for(;;);
} 
void *father(void *arg) 
{	
	while(1) 
	{		
		sem_wait(&remain);		
		sem_wait(&mutex);		
		printf("父親: 放蘋果之前, 剩余空間=%u, 蘋果數=%u\n", vremain--, vapple++);
		printf("父親: 放蘋果之后, 剩余空間=%u, 蘋果數=%u\n", vremain, vapple);
		sem_post(&mutex);		
		sem_post(&apple);	
		sleep(1);	
	}
}
void *mather(void *arg) 
{	
	while(1) 
	{		
		sem_wait(&remain);		
		sem_wait(&mutex);		
		printf("母親: 放梨子之前, 剩余空間=%u, 梨子數=%u\n", vremain--, vpear++);
		printf("母親: 放梨子之后, 剩余空間=%u, 梨子數=%u\n", vremain, vpear);	
		sem_post(&mutex);	
		sem_post(&pear);	
		sleep(2);	
	}
} 
void *son(void *arg) 
{	
	while(1) 
	{		
		sem_wait(&pear);	
		sem_wait(&mutex);    
		printf("兒子: 吃梨子之前, 剩余空間=%u, 梨子數=%u\n", vremain++, vpear--);	
		printf("兒子: 吃梨子之后, 剩余空間=%u, 梨子數=%u\n", vremain, vpear);	
		sem_post(&mutex);	
		sem_post(&remain);		
		sleep(3);
	}
} 
void *daughter(void *arg)
{	
	while(1) 
	{	
		sem_wait(&apple);	
		sem_wait(&mutex);
		printf("女兒: 吃蘋果之前, 剩余空間=%u, 蘋果數=%u\n", vremain++, vapple--);
		printf("女兒: 吃蘋果之前, 剩余空間=%u, 蘋果數=%u\n", vremain, vapple);	
		sem_post(&mutex);	
		sem_post(&remain);	
		sleep(3);	
	}
} 
void print_sem()
{	
	int val1, val2, val3;
	sem_getvalue(&remain, &val1);	
	sem_getvalue(&apple, &val2);	
	sem_getvalue(&pear, &val3);	
	printf("Semaphore: remain:%d, apple:%d, pear:%d\n", val1, val2, val3);
}

  編譯時記得要加上  -lpthread  否則sem_ 都是未定義的。

因為在主函數中做了一個死循環所以這個程序會一直跑下去

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM