前一篇文章概述了Linux 系统中信号量互斥编程,这篇文章正好是前一篇的姊妹篇----信号量同步。说它们是姊妹篇是因为它们都是利用了内核的信号量机制实现了进程间的通信。因为两者所解决的问题不同,因此它们使用的场景就会有所区别。
信号量互斥主要解决的问题是:进程间需要同时访问某种资源,但是它们对资源的操作会互相影响对方的操作结果,因此需要一种机制实现让进程在访问资源时能禁止其他进程访问相同的资源。而信号量同步则解决了另一个经典问题:生产者和消费者之间的协同工作问题。
首先描述一下生产者和消费者问题:进程A 负责生产产品(创建并写入文件),进程B 负责消费产品(复制文件),理想的流程是当进程A 创建并写好数据到文件以后,进程B就取走文件。但是两个进程运行时机的不确定往往让这个流程出现混乱,即进程A的生产工作还未完成(如只创建了文件但是没写入数据),进程B 就复制了文件,导致进程B 得到的是一个不完整的文件。事实上问题出现的原因就是两个进程间没有一种同步的机制。
信号量的提出就解决了这个问题。信号量的实现在前一篇文章中有详细介绍,这里只是指出运用信号量同步进程与互斥之间的不同,然后给出一个实例说明。
下面是笔者实现代码的思维导图:
与互斥不同的是,在给信号量赋初值时并不是赋值为1,而是赋值为0,并且在生产者中并没有获取信号量,而在消费者中也没有释放信号量。这样做是为了使两者的运行次序不会影响最后的结果。
可以分析:如果生产者程序先运行,它会依次创建并写入文件,假设此时消费者程序运行,但是此时信号量初值为0,因此消费者程序被挂起。当生产者程序执行完,释放信号量以后,消费者程序继续运行,最后得到正确的文件。
如果消费者程序先运行,它测试到信号量初值是0 ,因此直接被挂起,直到生产者程序运行完才继续运行,可知这样也能够得到正确的文件。
需要注意的是,在上图中的1.7 释放信号量里面,包含一个将初值置为0 的操作,这样是为了在下一次再运行这两个程序时,如果先运行消费者,初值仍然是0。
下面是笔者的测试代码:
生产者代码:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/sem.h> int main(int argc, char **argv) { int fd = 0; key_t key; int semid; struct sembuf ops; //创建信号量集合 key = ftok("/home/application/semapthore",1); semid=semget(key,1,IPC_CREAT); //将初值置为0 semctl(semid,0,SETVAL,0); //创建文件 fd = open("./product.txt",O_RDWR|O_CREAT,0755); //休息 sleep(20); //写入数据 write(fd,"Product is finished!",21); //关闭文件 close(fd); //释放信号量 ops.sem_num=0; ops.sem_op=1; ops.sem_flg = SEM_UNDO; semop(semid,&ops,1); semctl(semid,0,SETVAL,0); return 0; }
消费者代码;
#include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int main(int argc,char **argv) { key_t key; int semid; struct sembuf ops; //打开信号量集合 key = ftok("/home/application/semapthore",1); semid=semget(key,1,IPC_CREAT); //获取信号量 ops.sem_num = 0; ops.sem_op = -1; ops.sem_flg = SEM_UNDO; semop(semid,&ops,1); //消费文件 system("cp ./product.txt ./comsum/"); return 0; }