系列文章:
Linux IPC進程間通信(一):管道
Linux IPC進程間通信(二):共享內存
Linux IPC進程間通信(三):信號量
Linux IPC進程間通信(四):消息隊列
此篇文章參考於:Linux系統編程—信號量
前言
信號量的使用方法與 共享內存,消息隊列均不同,後者是主要方便進程間通信,而信號量的作用是 對共享資源的互斥訪問
當信號量的資源數量 <= 0 時,則會阻塞,直到資源數量 >0 時纔開始進行操作,另外信號量的操作均爲原子操作。
信號量的主要兩種用法(根據信號量初始資源數量的不同, 或者說有沒有利用到信號量的資源值):
-
互斥鎖(信號量的初始值爲1)
可以將信號量的資源數量設置爲1, P操作加鎖,V操作解鎖,主要用於實現對公共資源的互斥訪問 -
計數信號量(信號量的初始值 > 1)
信號量的初始值大於1時,進行P操作進行消費, V操作進行生產,當信號量的資源值<=0 時阻塞等待,主要用於實現生產者消費者案例
信號量的使用
int semget(key_t keys, int nsems, int flags)
創建一個(或多個信號量)
keys: 用來判斷是創建新的信號量還是用已有的信號量
nsems: 信號量的數量
flags: 創建的權限及特性
int semop(int semid, struct sembuf* sops, size_t nops)
將要進行的操作 sop 添加到信號量semid中
semid: 信號量的標識
sops: 添加的操作結構體
nops: 添加操作結構體的個數,若添加的操作只有1個,那麼 nops=1。
struct sembuf* sops 有三個成員
sem_num : 代表着要操作的信號量的下標(從0開始)
sem_op : 要進行的操作, 一般是 1 或 -1 , 也就是 P/V操作
sem_flg : 一般爲0, 表示undo
int semctl(int semid, int semnum, int cmd)
對信號量semid進行操作
- sennum 信號量集合中的下標,一般來說只有一個信號量,也就是爲 0
- cmd 執行的命令,一般有如 GETVAL , IPC_RMID, SETVAL, SETVALL 等操作
另外,我們通常會使用 ipcs
命令來查看信號量的使用:
通過以下示例程序可以更深入理解。
1.針對一個信號量集合中只有一個信號量的情況
int test1()
{
int ret;
int semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
ERROR_CHECK(semid, -1, "semget");
semctl(semid, 0, SETVAL, 10); //設置信號量的初始值爲10
struct sembuf sop;
sop.sem_num = 0; //下標爲0的信號量
sop.sem_op = 1; //V操作
sop.sem_flg = 0; //undo操作
ret = semop(semid, &sop, 1);
ERROR_CHECK(ret, -1, "semop");
cout << semctl(semid, 0, GETVAL) << endl;
ret = semctl(semid, 0, IPC_RMID);
ERROR_CHECK(ret, -1, "semctl");
return 0;
}
2.針對一個信號量集合中有多個信號量的情況
int test2()
{
int ret;
int semid = semget((key_t)0x1234, 3, 0666 | IPC_CREAT);
ERROR_CHECK(semid, -1, "semget");
unsigned short array[3] = {10, 20, 30}; //爲三個信號量設定初始值
semctl(semid, 0, SETALL, array);
//爲三個信號量分別 +1 +2 +3
struct sembuf sop[3];
sop[0].sem_num = 0;
sop[0].sem_op = 1;
sop[0].sem_flg = 0;
sop[1].sem_num = 1;
sop[1].sem_op = 2;
sop[1].sem_flg = 0;
sop[2].sem_num = 2;
sop[2].sem_op = 3;
sop[2].sem_flg = 0;
ret = semop(semid, sop, 3);
ERROR_CHECK(ret, -1, "semop");
cout << "第0個信號量的值: " << semctl(semid, 0, GETVAL) << endl;
cout << "第1個信號量的值: " << semctl(semid, 1, GETVAL) << endl;
cout << "第2個信號量的值: " << semctl(semid, 2, GETVAL) << endl;
semctl(semid, 0, IPC_RMID);
return 0;
}
信號量的用法一:互斥鎖
案例:兩個進程同時加一千萬(在共享內存中),並查看最終值是否爲兩千萬
int add_and_count()
{
int N = 10000000;
int shmid = shmget((key_t)0x1000, 4, 0666 | IPC_CREAT);
ERROR_CHECK(shmid, -1, "shmget");
int *sum = (int *)shmat(shmid, NULL, 0);
ERROR_CHECK(sum, (int *)-1, "shmat");
*sum = 0;
int semid = semget((key_t)0x1234, 1, 0666 | IPC_CREAT);
ERROR_CHECK(semid, -1, "semget");
semctl(semid, 0, SETVAL, 1);
struct sembuf P, V;
P = {0, -1, 0};
V = {0, 1, 0};
if (fork() > 0)
{
for (int i = 0; i < N; i++)
{
semop(semid, &P, 1);
(*sum)++;
semop(semid, &V, 1);
}
wait(NULL);
cout << "final sum = " << *sum << endl;
semctl(semid, 0, IPC_RMID);
shmctl(shmid, IPC_RMID, NULL);
}
else
{
for (int i = 0; i < N; i++)
{
semop(semid, &P, 1);
(*sum)++;
semop(semid, &V, 1);
}
}
return 0;
}
信號量的用法二 : 計數信號量
案例:生產者消費者
一個倉庫有10個空位置存放產品,生產者生產產品(沒有空位子時等待),消費者消費產品(沒有產品時等待)
int producer_consumer()
{
int ret;
int semid = semget((key_t)0x1234, 2, 0666 | IPC_CREAT);
ERROR_CHECK(semid, -1, "semget");
unsigned short array[2] = {10, 0}; //空位子,成品數
semctl(semid, 0, SETALL, array);
struct sembuf P1, V1, P2, V2;
P1 = {0, -1, 0};
P2 = {1, -1, 0};
V1 = {0, 1, 0};
V2 = {1, 1, 0};
//生產者
if (fork() > 0)
{
for (int i = 0; i < 5; i++)
{
semop(semid, &P1, 1);
cout << "Producing..." << endl;
semop(semid, &V2, 1);
cout << "after producing, space = " << semctl(semid, 0, GETVAL) << " , production = " << semctl(semid, 1, GETVAL) << endl;
sleep(2);
}
wait(NULL);
semctl(semid, 0, IPC_RMID);
}
//消費者
else
{
for (int i = 0; i < 5; i++)
{
semop(semid, &P2, 1);
cout << "Consuming..." << endl;
semop(semid, &V1, 1);
cout << "after consuming, space = " << semctl(semid, 0, GETVAL) << " , production = " << semctl(semid, 1, GETVAL) << endl;
sleep(1);
}
}
return 0;
}