執行緒同步3 ------ 訊號量實現程序或者執行緒之間的同步
基本概念
首先要注意,訊號量和訊號是完全兩碼事。訊號量是一個計數器,常用於處理程序或執行緒的同步問題,特別是對臨界資源訪問的同步。臨界資源可以簡單地理解為在某一時刻只能由一個程序或執行緒進行操作的資源。通常,程式對共享資源的訪問的程式碼只是很短的一段,但就是這一段程式碼引發了程序之間的競態條件。這段程式碼稱為關鍵程式碼段,或者臨界區。對程序同步,也就是確保任一時刻只有一個程序能進入關鍵程式碼段。
訊號量的值與相應資源的使用情況有關,當它的值大於0時,表示當前可用資源的數量,當它的值小於0時,其絕對值表示等待使用該資源的程序個數。訊號量的值僅能由pv操作來改變。在Linux下,pv操作通過呼叫函式semop實現。
訊號量是一種特殊的變數,它只支援2種操作:P操作(進入臨界區)和V操作(退出臨界區)。假設有訊號量SV,則對它的P、V操作含義如下:
- P(SV),如果SV的值大於0,意味著可以進入臨界區,就將它減1;如果SV的值為0,意味著別的程序正在訪問臨界區,則掛起當前程序的執行;;
- V(SV),當前程序退出臨界區時,如果有其他程序因為等待SV而掛起,則喚醒之;如果沒有,則將SV加1,之後再退出臨界區。
與訊號量相關的資料型別和函式
semid:訊號集的識別符號
struct semid_ds:訊號集資料結構
struct sembuf:對應一個特定訊號的操作
union semun:共用體變數,用以標示特定的操作
semget:建立或開啟訊號集
sem_p:訊號集的p操作函式
sem_v:訊號集的v操作函式
semop:pv操作通過呼叫函式semop實現
semctl:訊號集的控制函式,比如刪除訊號集,對訊號集的資料結構進行設定,獲取訊號集中訊號值等。
semget系統呼叫
此係統呼叫建立一個新的訊號量集,或者獲取一個已經存在的訊號量集。
key是一個鍵值,用以標識一個全域性唯一的訊號集,要通過訊號量通訊的程序需要使用相同的鍵值來建立/獲取該訊號量。#include <sys/sem.h> int semget(key_t key, int num_sems, int sem_flags);
num_sems指定要建立/獲取的訊號量集中訊號量的數目。如果是建立訊號量,則該值必須被指定。如果是獲取已經存在的訊號量,則可以將其設定為0。
sem_flags指定一組標誌。
semget呼叫成功會返回一個正整數值,它是訊號量集的識別符號;失敗時返回-1。
semop系統呼叫
此係統呼叫改變訊號量的值,即執行P、V操作。
#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_ops,size_t num_sem_ops);
sem_id就是上述semget呼叫返回的訊號量集識別符號。
sem_ops指向一個sembuf結構體型別的陣列。
semctl系統呼叫
此係統呼叫允許呼叫者對訊號量進行直接控制。
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
sem_id訊號量集識別符號。
sem_num指定被操作的訊號量在訊號量集中的編號。
command指定要執行的命令。
//在父、子程序之間使用訊號量來進行同步
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
union semun{
int val; //用於SETVAL命令,也即本例中的命令
struct semid_ds *buf; //用於IPC_STAT和IPC_SET命令
unsigned short int *array; //用於GETALL和SETALL命令
struct seminfo *__buf; //用於IPC_INFO命令
};
//op為-1時執行p操作,op為1時執行v操作
void pv(int sem_id,int op)
{
struct sembuf sem_b;
sem_b.sem_num=0; //訊號集中第0個訊號(也就是訊號集中的第一個訊號)
sem_b.sem_op=op; //指定操作型別,可以是1,0,-1。
sem_b.sem_flg=SEM_UNDO; //SEM_UNDO含義是,當程序退出時取消正在進行的semop操作。
semop(sem_id,&sem_b,1);
}
int main(int argc,char *argv[])
{
//IPC_PRIVATE是個特殊的鍵值(0)。這個名稱有點誤導(由於歷史原因),並不是“私有的”,應該稱為IPC_NEW。
int sem_id=semget(IPC_PRIVATE,1,0666);
union semun sem_un;
sem_un.val=1;
//將訊號量的semval值設定為semun.val,同時核心資料中的semid_ds.sem_ctime被更新
//SETVAL操作的是單個訊號量,是由識別符號sem_id指定的訊號量集中的第sem_num個訊號量(本例即為第0個訊號量)
semctl(sem_id,0,SETVAL,sem_un);
pid_t id=fork();
if(id<0){
return 1;
}else if(id==0){
printf("Child try to get binary sem\n");
pv(sem_id,-1);
printf("Child get the sem and would release it after 5 seconds\n");
sleep(5);
pv(sem_id,1);
exit(0);
}else{
printf("Parent try to get binary sem\n");
pv(sem_id,-1);
printf("Parent get the sem and would release it after 5 seconds\n");
sleep(5);
pv(sem_id,1);
}
waitpid(id,NULL,0);
semctl(sem_id,0,IPC_RMID,sem_un);
return 0;
}
執行結果: