信号量原理
- 保证多进程(线程)互斥访问某种共享资源(共享内存,文件)
- 一个用于协调同步互斥的计数器
- 与操作系统的PV操作类似
- 信号量的值等于临界区中资源的数量,进程进临界区前需要多少减多少,不够减就阻塞进阻塞队列
- 同属于System V IPC
API以及与信号量相关的各种数据结构
- 创建信号量集合
#include <sys/sem.h>
int semget(key_t key,int nsems,int flg);
参数表:
key:信号量的键值
nsems:集合中信号量的个数
flag:权限
返回值:
大于0:信号量集合ID
-1:失败
- 操作信号量集合
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd[,union semun arg]);
参数表:
semid:信号量集合ID
semnum:对集合中哪个信号量进行操作(索引类似数组下标)
cmd:命令
arg:根据cmd不同而不同
返回值:
非-1:cmd不同而不同
-1:失败
union semun
此共用体用于对应cmd的不同操作来返回(设置)不同的数值,此数据结构有些系统必须自行实现
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}
cmd | 操作 | arg |
---|---|---|
IPC_STAT | 获取semid_ds | arg.buf |
IPC_SET | 设置semid_ds | arg.buf |
IPC_RMID | 删除信号量集合 | NULL |
GETVAL | 返回semnum号信号量的值 | NULL |
SETVAL | 设置semnum号信号量的值 | arg.val |
GETALL | 返回集合中所有信号量的值 | arg.array |
SETALL | 设置集合中所有信号量的值 | arg.array |
GETPID | 返回semnum号信号量的sempid | NULL |
GETNCNT | 返回semnum号信号量的semncnt | NULL |
SETZCNT | 返回semnum号信号量的semzcnt | NULL |
赋初值示例代码(封装成函数)
sem_init(int sem_id,int semnum, int val)
{
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}initval;
initval.val = val;
if(semctl(semid,semnum,SETVAL,initval)==-1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
}
- PV操作
#include <sys/sem.h>
int semop(int semid,struct sembuf semarray[],size_t nops);
参数表:
semid:信号量集合ID
semarray:存放操作的数组,数组中每个元素代表一步操作
nops:操作的步数
返回值:
0:成功
-1:失败
struct sembuf结构体
此结构体存放具体的操作
struct sembuf
{
unsigned short sem_num;//对几号信号量操作
short sem_op;//5表示v5个资源,-2表示p2两资源
short sem_flg;//资源不够怎么办(阻塞还是出错)(IPC_NOWAIT或SEM_UNDO)
}
SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。有效防止死锁
PV操作示例代码(写进程与写进程之间的互斥)
每次一能有一个写进程进入临界区,只有一个信号量,初始值为1
void sem_p(int sem_id)
{//p操作
cout << "now lock" << endl;
struct sembuf action[1];//一步操作
action[0].sem_num = 0; //对0号信号量操作
action[0].sem_op = -1; //信号量减1,进入临界区
action[0].sem_flg = SEM_UNDO;
if(semop(sem_id,action,1)==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}
}
void sem_v(int sem_id)
{//v操作
cout << "now unlock" << endl;
struct sembuf action[1];//一步操作
action[0].sem_num = 0; //对0号信号量操作
action[0].sem_op = +1; //出临界区,信号量加一
action[0].sem_flg = SEM_UNDO;
if(semop(sem_id,action,1)==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}
}
- 示例代码
一个写进程,多个读进程互斥访问共享内存
写时临界区内不能有读
读时临界区内不能有写,但可以有读
写端:写进程负责共享内存创建删除,信号量创建删除
//写进程
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
using namespace std;
#define SHM_KEY 99
#define SEM_KEY 111
//共用体
union semun
{
int val;//信号量的值
struct semid_ds *buf;
unsigned short *array;
};
/************信号量初始化*******/
void sem_init(int sem_id,int semnum,int value)
{
union semun initval;
initval.val = value;
if(semctl(sem_id,semnum,SETVAL,initval)==-1 )
{
perror("semctl");
exit(EXIT_FAILURE);
}
}
/***************p操作,写前加锁*/
void sem_p(int sem_id)
{
cout << "now lock for wirte" << endl;
struct sembuf action[2];//一步操作
action[0].sem_num = 0; //对0号信号量(读锁)操作
action[0].sem_op = 0; //期待信号量为0(临界区内无进程在读)
action[0].sem_flg = SEM_UNDO;
action[1].sem_num = 1; //对1号信号量(写锁)操作
action[1].sem_op = +1; //表示写进程进入互斥区
action[1].sem_flg = SEM_UNDO;
if(semop(sem_id,action,2)==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}
}
/*****************v操作,写后减锁*/
void sem_v(int sem_id)
{
cout << "now unlock for wirte" << endl;
struct sembuf action[1];
action[0].sem_num = 1; //对1号互斥量(写锁)操作
action[0].sem_op = -1; //写进程退出临界区
action[0].sem_flg =SEM_UNDO;
if(semop(sem_id,action,1)==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}
}
int main()
{
int shm_id; //共享内存id
int sem_id; //信号量(集合)id
char * shm_ptr; //共享内存起始地址
int i=10; //while计数
/*共享内存创建*/
shm_id = shmget(SHM_KEY,1024,IPC_CREAT|0777);
if(shm_id == -1)
{
perror("shmget");
exit(EXIT_FAILURE);
}
/*共享内存连接*/
shm_ptr = (char *)shmat(shm_id,NULL,0);
if(shm_ptr == NULL)
{
perror("shmat");
exit(EXIT_FAILURE);
}
/*创建信号量集合*/
sem_id = semget(SEM_KEY,2,IPC_CREAT|0666);
/*信号量初始化*/
sem_init(sem_id,0,0);//表示临界区内的读进程数量
sem_init(sem_id,1,0);//表示临界区内的写进程数量
while(i>0)
{//循环写入10次
/*p操作*/
sem_p(sem_id);
/*****临界区**********************************************************************/
strcpy(shm_ptr,"111111");
sleep(2); //写进程的时间,此时读进程阻塞
/*****临界区**********************************************************************/
/*v操作*/
sem_v(sem_id);
i--;
}
shmctl(shm_id,IPC_RMID,NULL);//删除共享内存
semctl(sem_id,0,IPC_RMID,NULL);//删除信号量
}
读端:写进程循环写时,读端可以多次读来观察互斥情况
//读进程
#include <iostream>
using namespace std;
#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_KEY 99
#define SEM_KEY 111
//共用体
union semun
{
int val;//信号量的值
struct semid_ds *buf;
unsigned short *array;
};
/***************p操作,读前加锁*/
void sem_p(int sem_id)
{
cout << "now lock for read" << endl;
struct sembuf action[2];
action[0].sem_num = 1; //对1号信号量(写锁)操作,
action[0].sem_op = 0; //期待没有进程在写
action[0].sem_flg = SEM_UNDO;
action[1].sem_num = 0; //对0号信号量(写锁)操作
action[1].sem_op = +1; //一个读进程进入临界区
action[1].sem_flg = SEM_UNDO;
if(semop(sem_id,action,2)==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}
}
/*****************v操作,读后减锁*/
void sem_v(int sem_id)
{
cout << "now unlock for read" << endl;
struct sembuf action[1];
action[0].sem_num = 0; //对0号互斥量(读锁)操作
action[0].sem_op = -1; //一个读进程退出临界区
action[0].sem_flg =SEM_UNDO;
if(semop(sem_id,action,1)==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}
}
int main()
{
int shm_id;
int sem_id;
char * shm_ptr;
shm_id = shmget(SHM_KEY,1024,0777);
if(shm_id == -1)
{
perror("shmget");
exit(EXIT_FAILURE);
}
shm_ptr = (char *)shmat(shm_id,NULL,0);
if(shm_ptr == NULL)
{
perror("shmat");
exit(EXIT_FAILURE);
}
//引用信号量集合(写进程已经创建好并初始化)
sem_id = semget(SEM_KEY,2,0);
/*p操作*/
sem_p(sem_id);
/*****临界区***/
cout << shm_ptr <<endl;
/******临界区**/
/*V操作*/
sem_v(sem_id);
/*共享内存摆脱*/
shmdt(shm_ptr);
return 0;
}
网友评论