信号量的最主要的目的是被用来进行同步和互斥的。
同步和互斥这两个概念在我进程间通信的时候解释过,记不起来的小伙伴可以点这里。
在这里,解释一下信号量为什么能达到互斥的效果,以及信号量的本质。
信号量本质上来说,就是一个计数器,其本身除了标志资源是否存在没有其他意义。
为什么这么讲呢?
举个栗子:你有100亿人民币,想象一下就是这么膨胀!那么正常人都不会把这100亿的钞票带在身上吧,我们的正常路数就是放在银行,然后美滋滋的刷卡。然后看着自己的银行卡余额,快乐生活。这个时候,你的银行卡余额就相当于是信号量,他只是一个标识数字,但是钱并不在你身上,只是在你需要消费的时候,银行的资金足以支持你的消费。但是假如有一天,你存钱的这家银行把你的钱投在别的地方亏空了,而你这个时候又刚好需要 一笔大额支出,那就有可能发生银行拿不出钱来给你的消费的情况。当然,这种情况是极少发生的。
操作系统会替我们保证信号量存在,资源也存在,其作用与银行类似。
在我们的计算机世界中,临界资源这一部分有时候是我们自己定义的,因为我们要保证某些操作的原子性。
原子性:保证某个操作是一个整体,要么全部做完,要么根本不做,不会出现第三种状态。
同理,我们用信号量保证我们访问临界资源的操作是原子操作,而操作系统会为我们保证信号量的变动也是原子操作。这就是为什么信号量能够达到互斥的效果。
对信号量的操作被称为P、V操作,这是荷兰科学家迪杰斯特拉提出的,在荷兰语里,P是申请资源,V是释放资源的意思。
所以执行P操作,信号量的值会减少,执行V操作,信号量的值会增加。
信号量集函数:
semget函数:用来创建和访问一个信号量集。
int semget(key_t key,int nsems, int semflg);
//key是信号量集的名字
//nsems信号量集的个数
//semflg权限标志
//返回值:成功返回一个整数,失败返回-1
semctl函数:用于控制信号量集
int semctl(int semid, int semnum, int cmd,...);
//semid由semget返回
//semnum信号量集中信号量的序号
//cmd采取的动作
//返回值:成功0,失败-1
semop函数:用来创建和访问一个信号量集
int semop(int semid, struct sembuf* sop, unsigned nsops);
//sop指向结构数值的指针
//nsops信号量的个数
//返回值:成功0,失败-1
来段代码实例:这段代码功能是使得不同在屏幕上连续输出两个a或者两个b,如果是单个进程做到这一点显然不难,不同进程就必须要加上PV操作了!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int id;
void p()
{
struct sembuf sem[1] = {0, -1, 0};
semop(id, sem, 1);
}
void v()
{
struct sembuf sem[1] = {0, 1, 0};
semop(id, sem, 1);
}
void print(char c)
{
int i = 0;
for(; i<10; ++i)
{
p();
printf("%c",c);
fflush(stdout);
sleep(rand()%3);
printf("%c",c);
fflush(stdout);
v();
sleep(rand()%3);
}
}
union semnu{ int val; };
int main()
{
srand(getpid());
id = semget(219, 1, IPC_CREAT|0600);
if( id == -1) perror("semget"),exit(1);
union semnu sem;
sem.val = 1;
semctl(id, 0, SETVAL, sem);
pid_t pid = fork();
if(pid == 0){
print('a');
}else{
print('b');
}
return 0;
}