信号量
(1)什么是信号量
当线程运行在多进程系统上时,不可避免的会产生临界区域,如果不加控制会产生不可预计的后果。为了防止多个程序同时访问同一资源,由避免CPU资源的浪费,定义了一种特殊的变量,它只能取正整数值,并且程序对其访问都是原子操作。这样就保证了进程在对临界资源访问时的安全性。
最简单的信号量只能取0和1两个值,即二进制信号量。二进制信号量的操作被叫做PV操作,P操作就是信号量变量的值大于0,就给它减去0;如果它的值等于0,就挂起该进程。V操作就是如果有其它进程因为等待信号量而被挂起,就让它恢复运行,如果没有进程因为它而被挂起,就给它加1。
(2)信号量的实现
用到了一下几个函数
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semctl(int sem_id, int sem_num, int command, …)
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops)
其中,key的作用很像一个文件名,它代表程序可能要使用某个资源,如果多个程序使用相同的key值,它将负责协调工作。
semget函数的作用是创建一个新的信号量或者获取一个已有信号量的key值。创建成功则返回一个非0值,即信号量标识符,否则返回-1。
semop函数用于改变信号量的值。它是利用一个结构体来设置信号量的值。结构体如下所示
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
}
其中,sem_num是信号的编号,一般为0。sem_op是在一次操作中需要改变的值,通常只会用到两个值,一个是-1,也就是P操作,它等待信号量变为可用。一个是+1,也就是V操作,它发送信号表示信号量现在可用。
最后一个成员sem_flg通常被设置为SEM_UNDO,这样可以让系统追踪该信号量,若进程在没有释放信号量的情况下终止,则系统会自动释放进程所持有的信号量。
semctl函数用来直接控制信号量信息。常用来对信号量进行初始化和删除。
代码示例
信号量常用于进程间控制,为了提高代码的重用性,将函数封装为P操作和V操作。代码如下所示:
头文件定义了union semun结构
#ifndef __SEM_H
#define __SEM_H
typedef union SemUn
{
int val;
}SemUn;
int SemGet(int key, int semVal[], int nsems);
int SemP(int semid, int index[], int sems);
int SemV(int semid, int index[], int sems);
int SemDel(int semid);
#endif
#include "sem.h"
#include <sys/sem.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>
int SemGet(int key, int semVal[], int nsems)
{
int semid = semget((key_t)key, nsems, 0664);
if(semid == -1)
{
semid = semget((key_t)key, nsems, 0664|IPC_CREAT);
assert(semid != -1);
int i = 0;
for(; i < nsems; ++i)
{
SemUn un;
un.val = semVal[i];
semctl(semid, i, SETVAL, un);
}
}
return semid;
}
int SemP(int semid, int index[], int sems)
{
struct sembuf *buf = (struct sembuf*)malloc(sizeof(struct sembuf) * sems);
assert(buf != NULL);
int i = 0;
for(; i < sems; ++i)
{
buf[i].sem_num = index[i];
buf[i].sem_op = -1; // P
buf[i].sem_flg = SEM_UNDO;
}
if(-1 == semop(semid, buf, sems))
{
free(buf);
return -1;
}
free(buf);
return 0;
}
int SemV(int semid, int index[], int sems)
{
struct sembuf *buf = (struct sembuf*)malloc(sizeof(struct sembuf) * sems);
assert(buf != NULL);
int i = 0;
for(; i < sems; ++i)
{
buf[i].sem_num = index[i];
buf[i].sem_op = 1; // P
buf[i].sem_flg = SEM_UNDO;
}
if(-1 == semop(semid, buf, sems))
{
free(buf);
return -1;
}
free(buf);
return 0;
}
int SemDel(int semid)
{
return semctl(semid, 0, IPC_RMID);
}
利用fork创建进程来实现父进程与子进程的交替执行。
/*************************************************************************
> File Name: fork.c
> Author: wangchen
> Mail: [email protected]
> Created Time: Sat 23 Mar 2019 05:16:56 PM CST
************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include "sem.h"
int main()
{
int semVal = 1;
int semid = SemGet(1234, &semVal, 1);
printf("semid = %d\n", semid);
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
int i = 0;
for(; i < 10; ++i)
{
int index = 0;
SemP(semid, &index, 1);
printf("A\n");
sleep(1);
SemV(semid, &index, 1);
}
}
else
{
int i = 0;
for(; i < 10; ++i)
{
int index = 0;
SemP(semid, &index, 1);
printf("B\n");
sleep(1);
SemV(semid, &index, 1);
}
}
}