信号量
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。
P(sv)和V(sv),执行方式如下:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。
例如:若两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
信号量相关的函数:
1、 创建一个信号量或访问一个已经存在的信号量集:
int semget (key_t key, int nsem, int oflag) ;
返回值是一个称为信号量标识符的整数,semop和semctl函数将使用它。参数nsem指定集合中的信号量数。(若用于访问一个已存在的集合,那就可以把该参数指定为0)参数oflag可以是SEM_R(read)和SEM_A(alter)常值的组合。(打开时用到),也可以是IPC_CREAT或IPC_EXCL ;
2、打开信号量
使用semget打开一个信号量集后,对其中一个或多个信号量的操作就使用semop(op--operate)函数来执行。
int semop (int semid, struct sembuf * opsptr, size_t nops) ;
参数opsptr是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示
union semun
{
int val;// cmd == SETVAL
struct semid_ds *buf// cmd == IPC_SET或者 cmd == IPC_STAT
ushort *array;// cmd == SETALL,或 cmd = GETALL
};
val只有cmd ==SETVAL时才有用,此时指定的semval = arg.val。
注意:
当cmd == GETVAL时,semctl函数返回的值就是我们想要的semval。千万不要以为指定的semval被返回到arg.val中。array指向一个数组,当cmd==SETALL时,就根据arg.array来将信号量集的所有值都赋值;当cmd ==GETALL时,就将信号量集的所有值返回到arg.array指定的数组中。buf 指针只在cmd==IPC_STAT 或IPC_SET 时有用,作用是semid 所指向的信号量集(semid_ds机构体)。一般情况下不常用, 另外,cmd == IPC_RMID还是比较有用的。
//comm.h
#ifndef __COMM__
#define __COMM__
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#include<string.h>
#include<errno.h>
#define PATHNAME "."
#define ID 6666
union Sem
{
int val;
struct Semid_ds* buf;
unsigned short* array;
struct seminfo* _buf;
};
int CreatSemSet(int nums);//创建消息队列
int GetSemSet();
int InitSemSet(int semid,int which,int value);
int P(int semid,int which);
int V(int semid,int which);
int DestorySemSet(int semid);
#endif
//comm.c
#include"comm.h"
static int CommSemSet(int nums,int flags)
{
key_t _k = ftok(PATHNAME,ID);
if(_k<0)
{
perror("ftok");
sleep(2);
return -1;
}
int semid = semget(_k,nums,flags);
)
if(semid<0)
{
perror("semget");
sleep(2);
return -2;
}
return semid;
}
int CreatSemSet(int nums)//创建消息队列
{
return CommSemSet(nums,IPC_CREAT | IPC_EXCL | 0666);
}
int GetSemSet()
{
return CommSemSet(0,0);
}
int InitSemSet(int semid,int which,int value) //初始化消息队列
{
union Sem _sem;
_sem.val = value;
if(semctl(semid,which,SETVAL,_sem)<0)
{
perror("semctl");
sleep(2);
return -1;
}
return 0;
}
static int CommPV(int semid,int which,int op)
{
struct sembuf _sf;
_sf.sem_op = op;
_sf.sem_num = which;
_sf.sem_flg = 0;
return semop(semid,&_sf,1);
}
int P(int semid,int which) //P操作
{
return CommPV(semid,which,-1);
}
int V(int semid,int which) //V操作
{
return CommPV(semid,which,1);
}
int DestorySemSet(int semid) //释放消息队列
{
if(semctl(semid,0,IPC_RMID,NULL)<0)
{
perror("semctl");
sleep(2);
return -1;
}
return 0;
}
//sem.c
#include"comm.h"
int main()
{
int semid = CreatSemSet(1);
InitSemSet(semid,0,1);
pid_t id = fork();
if(id==0)
{
int semid = GetSemSet();
while(1)
{
// P(semid,0);
printf("A");
fflush(stdout);
usleep(23445);
printf("A ");
fflush(stdout);
usleep(30000);
// V(semid,0);
}
}
else
{
while(1)
{
// P(semid,0);
printf("B");
fflush(stdout);
usleep(30000);
printf("B ");
fflush(stdout);
usleep(30000);
// V(semid,0);
}
pid_t ret = waitpid(id,NULL,0);
if(ret>0)
{
printf("proc is done\n");
}
}
return 0;
}
加入P操作、V操作之后结果如下:
删除信号量:
当操作信号量(semop)时,sem_flg可以设置SEM_UNDO标识;SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。(即当操作的进程推出后,该进程对sem进行的操作将被取消,sem将回到初始状态)。