進程間通信之信號量
資源競爭
多個進程競爭同一資源時,會發生資源競爭。
資源競爭會導致進程的執行出現不可預測的結果。
臨界資源
不允許同時有多個進程訪問的資源, 包括硬件資源 (CPU、內存、存儲器以及其他外
圍設備) 與軟件資源(共享代碼段、共享數據結構)
臨界區
多個進程共享的資源被稱為臨界資源,
這些資源被保護在一個臨界區中,
只有進入臨界區的進程才能訪問臨界資源。
信號量
信號量是一種進程間通信機制,用于協調對共享資源的訪問。
多進程對stdout資源的競爭
//多進程對stdout資源的競爭#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>int main(){pid_t cpid;cpid = fork();//創建子進程if(cpid < 0){printf("fork error\n");//fork失敗exit(EXIT_FAILURE);//EXIT_FAILURE表示程序運行失敗} else if(cpid == 0){//子進程while(1){printf("------------------------\n");printf("C Start.\n");sleep(1);printf("C End.\n");printf("------------------------\n");}} else{//父進程while(1){printf("------------------------\n");printf("P Start.\n");sleep(1);printf("P End.\n");printf("------------------------\n");}wait(NULL); //等待子進程結束}return 0;
}
代碼的輸出混亂:
------------------------
P Start.
------------------------
C Start.
P End.
------------------------
C End.
------------------------
------------------------
P Start.
------------------------
C Start.
P End.
C End.
------------------------
------------------------
同步和互斥
互斥
互斥是指進程獨占資源,使得其他進程無法訪問該資源。
同步
同步是指進程間通信,用于協調進程的執行。
同步在互斥的基礎上增加了進程對臨界資源的訪問順序
進程主要的同步與互斥手段是信號量
信號量
信號量,由內核維護的整數,其值被限制為大于或等于0;
信號可以執行一下操作:
- 將信號量設置成一個具體的值;
- 在信號量當前的基礎上加上一個數值;
- 在信號量當前值的基礎上減上一個數值;
- 等待信號量的值為0;
一般信號量分為
- 二值信號量:一般指的是信號量值為1,可以理解為只對應一個資源
- 計數信號量:一般指的是值大于等于2,可以理解為對應多個資源
在linux系統中使用ipcs -s 查詢系統中信號量
創建信號量集合
調用 semget() 函數
函數頭文件:
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>int semget(key_t key, int nsems, int semflg);
函數功能:創建一個信號量集合;
函數參數:
- key: 信號量集合的鍵值, 用于標識信號量集合;由ftok()函數生成;
- nsems: 信號量集合中信號量的個數;
- semflg: 信號量集合的標志位, 用于設置信號量集合的屬性;
-
- IPC_CREAT: 如果key對應的信號量集合不存在, 則創建新的信號量集合;
-
- IPC_EXCL: 如果key對應的信號量集合已經存在, 則返回-1;
-
- 權限標志
函數返回值:
- 成功: 返回信號量集合的ID;
- 失敗: 返回-1, 并設置errno;
//多進程對stdout資源的競爭#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>#define MSG_PATH "/home/gopher"
#define MSG_ID 88
int main(){key_t key;//通過文件路徑和ID生成key,key= ftok(MSG_PATH,MSG_ID);if(key==-1){printf("ftok()");exit(EXIT_FAILURE);}//創建信號量集合,包含了一個信號量,編號為0int semid=semget(key,1,IPC_CREAT|0666);if(semid==-1){printf("semget()");exit(EXIT_FAILURE);}return 0;
}
創建出一個信號量集合,包含了一個信號量,編號為0
初始化信號量
調用 semctl() 函數
函數頭文件:
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>int semctl(int semid, int semnum, int cmd, ... /* arg */ );
函數功能:對信號量集合中的信號量進行操作;根據cmd 決定當前函數的功能;
函數參數:
- semid: 信號量集合的ID;
- semnum: 信號量的編號;編號從0開始;
- cmd: 信號量操作命令;
- SETVAL:設置信號量的值。
- GETPID:返回最后一個執行 semop 操作的進程的PID。
- GETVAL:返回指定信號量的值。
- GETALL:返回信號量集中所有信號量的值。
- GETNCNT:返回正在等待信號量增加的進程數。
- GETZCNT:返回正在等待信號量變為零的進程數。
- SETALL:設置信號量集中所有信號量的值。
- IPC_STAT:獲取信號量集的狀態信息。
- IPC_SET:設置信號量集的狀態信息。
- IPC_RMID:刪除信號量集。
- … :是屬于可變參參數列表,根據不同的命令有不同的參數;
函數返回值:
-
成功: 根據不同的cmd, 返回不同的結果;
-
GETPID:返回等待最后一個 semop 操作的進程的 PID。
GETVAL:返回指定信號量的值。
ls
GETALL:如果成功,返回 0。GETNCNT:返回正在等待增加信號量值的進程數量。
GETZCNT:返回正在等待信號量值為零的進程數量。
IPC_STAT:如果成功,返回 0。
IPC_SET:如果成功,返回 0。
IPC_RMID:如果成功,返回 0。
SETVAL:如果成功,返回 0。
SETALL:如果成功,返回 0。
-
失敗: 返回-1, 并設置errno;
//多進程對stdout資源的競爭#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>#define MSG_PATH "/home/gopher"
#define MSG_ID 88union semun{int val;
};
int main(){key_t key;//通過文件路徑和ID生成key,key= ftok(MSG_PATH,MSG_ID);if(key==-1){printf("ftok()");exit(EXIT_FAILURE);}//創建信號量集合,包含了一個信號量,編號為0int semid=semget(key,1,IPC_CREAT|0666);if(semid==-1){printf("semget()");exit(EXIT_FAILURE);}union semun s;//定義一個聯合體,用于設置信號量的值s.val=1;//設置信號量的值為1int ret=semctl(semid,0,SETVAL,s);//設置semid信號集中的第編號為0的信號量的值為1if(ret==-1){printf("semctl()");exit(EXIT_FAILURE);}return 0;
}
信號量操作
- 信號量可以進?以下操作:
- 對信號量的值加 1
- 對信號量的值減 1
- 等待信號量的值為 0
調用 semop() 函數
函數頭文件:
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>int semop(int semid, struct sembuf *sops, size_t nsops);
函數功能:對信號量集合中的信號量進行操作;
函數參數:
- semid: 信號量集合的ID;
- sops: 信號量操作結構體指針
- nsops: 信號量操作結構體的個數;
函數返回值:
- 成功: 返回 0;
- 失敗: 返回-1, 并設置errno;
struct sembuf *sops: 信號量操作結構體指針
struct sembuf
{unsigned short int sem_num;//信號量編號,從0開始short int sem_op; //信號量操作//-1:占用資源// +1:釋放資源// 0:等待資源short int sem_flg; //信號量操作標志位//IPC_NOWAIT:非阻塞,在信號量的值為0時,立即返回// SEM_UNDO:在進程終止時,會自動釋放信號量
};
信號量集合刪除
調用 semctl() 函數 ,設置命令為 IPC_RMID
在使用 semctl() 函數刪除信號量集合時,需要注意第三個參數會被忽略
信號量互斥應用
使用信號量實現進程間互斥,同一時間只有一個進程訪問臨界資源
1.創建sem.h
#ifndef _mySEM_H_
#define _mySEM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>//創建信號量集
int sem_create(int names,unsigned short value[]);
//占用信號量
int sem_p(int semid,int semnum);
//釋放信號量
int sem_v(int semid,int semnum);
//刪除信號量集
int sem_delete(int semid);#endif /* _SEM_H_ */
2.創建sem.c
#include "sem.h"union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};//創建信號量集
//@param names 信號量集的個數
//@param value 信號量集的初始值
//@return 成功返回信號量集的id,失敗返回-1
int sem_create(int names,unsigned short value[]){key_t key;//創建keykey= ftok(".",88);if (key == -1){perror("ftok");return -1;}//創建信號量集int semid;semid = semget(key,names,IPC_CREAT|0666);//參數:key,信號量集的個數,權限if (semid == -1){perror("semget");return -1;}union semun s; //定義union semuns.array = value;//將value數組賦值給union semun的array成員//初始化信號量集int ret=semctl(semid,0,SETALL,s);//這個操作將value數組中的值設置到信號量集中if (ret == -1){perror("semctl");return -1;}return semid;}//占用信號量
//@param semid 信號量集的id
//@param semnum 信號量的編號
int sem_p(int semid,int semnum){struct sembuf sem_b;//定義一個信號量操作結構體sem_b.sem_num=semnum;//信號量編號sem_b.sem_op= -1;//占用資源sem_b.sem_flg=SEM_UNDO;//在進程終止時,會自動釋放信號量//操作1個信號量,如果操作多個信號量,需要創建sembuf結構體的數組int r= semop(semid,&sem_b,1); //失敗返回-1,并設置errno return r;
}
//釋放信號量
int sem_v(int semid,int semnum){struct sembuf sem_b;//定義一個信號量操作結構體sem_b.sem_num=semnum;//信號量編號sem_b.sem_op= 1;//釋放資源sem_b.sem_flg=SEM_UNDO;//在進程終止時,會自動釋放信號量int r= semop(semid,&sem_b,1); //操作1個信號量,如果操作多個信號量,需要創建sembuf結構體的數組//失敗返回-1,并設置errno return r;
}
//刪除信號量集
int sem_delete(int semid){int r= semctl(semid,0,IPC_RMID); //刪除信號量集return r;
}
3.創建main.c
// 多進程對stdout資源的競爭#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include "sem.h"
int main()
{int semid;// 信號量IDunsigned short values[] = {1};// 信號量初始值semid = sem_create(1, values);if(semid == -1 ){printf("sem_create error\n");exit(EXIT_FAILURE);}pid_t cpid;// 子進程IDcpid = fork(); // 創建子進程if (cpid < 0){printf("fork error\n"); // fork失敗exit(EXIT_FAILURE); // EXIT_FAILURE表示程序運行失敗}else if (cpid == 0){ // 子進程while (1){sem_p(semid,0);printf("------------------------\n");printf("C Start.\n");sleep(1);printf("C End.\n");printf("------------------------\n");sem_v(semid,0);}}else{ // 父進程while (1){sem_p(semid,0);printf("------------------------\n");printf("P Start.\n");sleep(1);printf("P End.\n");printf("------------------------\n");sem_v(semid,0);}wait(NULL); // 等待子進程結束}return 0;
}
4.編譯運行
------------------------
P Start.
P End.
------------------------
------------------------
C Start.
C End.
------------------------
------------------------
P Start.
P End.
------------------------
------------------------
C Start.
C End.
----------
信號量同步應用
同步在互斥的基礎上增加了進程對臨界資源的訪問順序
進程主要的同步與互斥手段是信號量
示例:
創建??進程,輸出 “ABA” 字符串,具體需求如下:
?進程 輸出 A
?進程 輸出 B
?進程 輸出 A ,輸出換?
能夠循環輸出 “ABA” 字符
基本思路:
通過創建?個信號量集合,包含 2 個信號量,?個信號量 編號為 0
(SEM_CONTROL_P)控制?進程的運?與暫停,?個信號量 編號為 1
(SEM_CONTROL_C) 控制?進程的運?與暫停
// 多進程對stdout資源的競爭#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include "sem.h"#define SEM_C = 1
#define SEM_P = 0
// todo 創建一個信號量集合,集合中兩個信號量,信號量0的值是1,信號量1的值是0;
int main()
{int semid; // 信號量IDunsigned short values[2] = {1, 0}; // 信號量初始值// todo 創建一個信號量集合,集合中兩個信號量,信號量編號0的值是1,信號量編號1的值是0;semid = sem_create(2, values);if (semid == -1){printf("sem_create error\n");exit(EXIT_FAILURE);}pid_t cpid; // 子進程IDcpid = fork(); // 創建子進程if (cpid < 0){printf("fork error\n"); // fork失敗exit(EXIT_FAILURE); // EXIT_FAILURE表示程序運行失敗}else if (cpid == 0){ // 子進程while (1){sem_p(semid, 1); //?占用信號量編號1,信號量編號1的值初始是0 ,在這里阻塞,等待父進程操作printf("B");fflush(stdout); // 刷新緩沖sem_v(semid, 0); //!釋放信號量編號0,信號量編號0的值 0=>1,此時父進程不再阻塞,第二次占用0}}else{ // 父進程while (1){//@param semid 信號量集的id//@param semnum 信號量的編號sem_p(semid, 0); //?占用信號量編號0,信號量編號0的值 1=>0printf("A");fflush(stdout); // 刷新緩沖sem_v(semid, 1); //?釋放信號量編號1,信號量編號1的值 0=>1,此時子進程不再阻塞sem_p(semid, 0); //!第二次占用信號量編號0,信號量編號0的值是0,在這里阻塞,等待子進程的操作printf("A\n");fflush(stdout); // 刷新緩沖sem_v(semid, 0);sleep(1);}wait(NULL); // 等待子進程結束}return 0;
}
0的值 0=>1,此時父進程不再阻塞,第二次占用0}}else{ // 父進程while (1){//@param semid 信號量集的id//@param semnum 信號量的編號sem_p(semid, 0); //?占用信號量編號0,信號量編號0的值 1=>0printf("A");fflush(stdout); // 刷新緩沖sem_v(semid, 1); //?釋放信號量編號1,信號量編號1的值 0=>1,此時子進程不再阻塞sem_p(semid, 0); //!第二次占用信號量編號0,信號量編號0的值是0,在這里阻塞,等待子進程的操作printf("A\n");fflush(stdout); // 刷新緩沖sem_v(semid, 0);sleep(1);}wait(NULL); // 等待子進程結束}return 0;
}