一、前言
多线程中,线程同步问题,我们用互斥量,通过线程锁来解决,在进程中我们通过共享内存进行进程间的数据传递,也会遇到和线程同步类似的问题,这是进程同步,这时我们也时需要通过互斥锁来保护数据传输安全,信号量就是在进程中使用的锁。
二、信号量概述
Dijkstra提出的信号量(Semaphores)机制是一种卓有成效的进程同步工具,在长期广泛的应用中,信号量机制得到了极大的发展,它从整型信号量经记录型信号量,进而发展成为“信号量集机制”,信号量机制已经被广泛的应用到单处理机和多处理机系统以及计算机网络中。
三、原理
二进制信号量(binary semaphore):只允许信号量取0或1值
信号量是一种变量,它只能取正整数值,对这些正整数只能进行两种操作:等待和信号
用两种记号来表示信号量的这两种操作:
P(semaphore variable) :代表等待 -1
V(semaphore variable) :代表信号 +1
四、使用的函数
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1.信号量创建
int semget(key_t key,int nsems,int flags)//成功返回信号集id,错误返回-1
功能:创建一个新的信号量或获取一个已经存在的信号量的键值。
参数key:是一个整数值,不相关的进程将通过这个值去访问同一个信号量
参数nsems:需要使用的信号量个数,它几乎总是取值为1
参数flags:是一组标志,其作用与open函数的各种标志很相似,它低端的九个位是该信号量的权限,其作用相当于文件的访问权限,可以与键值IPC_CREATE做按位的OR操作以创建一个新的信号量(IPC_CREAT|0766)
2.信号量设置值
int semop ( int sem_id,struct sembuf *sem_ops,size_t num_sem_ops,);
功能:改变信号量的键值
参数sem_id:是该信号量的标识码,也就是semget函数的返回值
参数sem_ops:是个指向一个结构数值的指针
参数Semop:调用的一切动作都是一次性完成的,这是为了避免出现因使用了多个信号量而可能发生的竞争现象
其中第二个参数的结构体如下
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
};
3.信号量控制
int semctl(int sem_id,int sem_num,int command,…);
功能:允许我们直接控制信号量的信息
参数sem_id:是由semget函数返回的一个信号量标识码
参数sem_num:信号量的编号,如果在工作中需要使用到成组的信号量,就要用到这个编号;它一般取值为0,表示这是第一个也是唯一的信号量
参数comman:将要采取的操作动作
如果需要第四个参数则需要定义以下结构体
union semun
{
int val; //使用的值
struct semid_ds *buf; //IPC_STAT、IPC_SET 使用的缓存区
unsigned short *arry; //GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用的缓存区
};
semctl函数里的command可以有许多不同的值,下面这两个是比较常用的:
SETVAL:用来把信号量初始化为一个已知的值,这个值在semun结构里是以val成员的面目传递的。
IPC_RMID:删除一个已经没有人继续使用的信号量标识码
五、测试使用
我们写两个工程,分别同时运行来展示两个进程加了锁的使用效果。
工程一:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
using namespace std;
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) */
};
//信号量创建
int sem_create(key_t key, int nsems)
{
int res = semget(key,nsems,IPC_CREAT|0777);
if (res < 0)
{
perror("semget error");
}
return res;
}
//信号量设置值
int sem_setval(int semid,int semindex,int val )
{
union semun arg;
arg.val = val;
int res = semctl(semid, semindex, SETVAL, arg);
if (res < 0)
{
perror("semctl error");
}
return res;
}
//信号量p操作-1
int sem_p(int semid, int semindex)
{
struct sembuf buf = {semindex,-1,SEM_UNDO};
int res = semop(semid,&buf,1 );
if (res < 0)
{
perror("semop error");
}
return res;
}
//信号量v操作+1
int sem_v(int semid, int semindex)
{
struct sembuf buf = { semindex,1,SEM_UNDO };
int res = semop(semid, &buf, 1);
if (res < 0)
{
perror("semop error");
}
return res;
}
int main()
{
//先创建一个信号量,信号量nsems长度给1
int semid = sem_create((key_t)1001, 1);
//设定信号量数据,第0个下标的数据为1,
sem_setval(semid, 0, 1);
//加锁,下标为0的进行加锁,原来为1,p操作进行-1,为0
sem_p(semid, 0);
for (size_t i = 0; i < 30; i++)
{
cout << "第一个工程操作" << i << endl;
sleep(1);
}
//解锁,下标为0的进行解锁,0,v操作+1,为1
sem_v(semid, 0);
return 0;
}
工程二:
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
using namespace std;
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) */
};
//信号量创建
int sem_create(key_t key, int nsems)
{
int res = semget(key, nsems, IPC_CREAT | 0777);
if (res < 0)
{
perror("semget error");
}
return res;
}
//信号量设置值
int sem_setval(int semid, int semindex, int val)
{
union semun arg;
arg.val = val;
int res = semctl(semid, semindex, SETVAL, arg);
if (res < 0)
{
perror("semctl error");
}
return res;
}
//信号量p操作-1
int sem_p(int semid, int semindex)
{
struct sembuf buf = { semindex,-1,SEM_UNDO };
int res = semop(semid, &buf, 1);
if (res < 0)
{
perror("semop error");
}
return res;
}
//信号量v操作+1
int sem_v(int semid, int semindex)
{
struct sembuf buf = { semindex,1,SEM_UNDO };
int res = semop(semid, &buf, 1);
if (res < 0)
{
perror("semop error");
}
return res;
}
int main()
{
//1001信号量存在就访问,否则创建
int semid = sem_create((key_t)1001, 1);
//加锁
sem_p(semid, 0);
for (size_t i = 0; i < 30; i++)
{
cout << "第二个工程操作" << i << endl;
sleep(1);
}
//解锁
sem_v(semid, 0);
return 0;
}
运行效果:
先运行工程1,再马上运行工程2,要是没加锁的话,应改两个工程都会直接开始打印
结果是工程1运行完,工程2才开始打印,说明上锁成功,工程1运行上了锁,使得信号量1001的下标为0的数值p操作减1,变为0,此时工程二走到加锁的地方时,因为二进制信号量(binary semaphore):只允许信号量取0或1值,此时信号量1001的下标为0的数值为0,不能-1了,造成阻塞,直到工程1解锁,使得信号量1001的下标为0的数值为1,工程二能够进行-1,才开始走入打印函数。