开发环境
Red Hat Enterprise Linux Server release 7.0 (Maipo)
参考书籍
《Linux高性能服务器编程》
关于信号量的应用请看:https://blog.csdn.net/qq_42418668/article/details/95177446
问题的提出
当多个进程同时访问系统上的某个资源的时候,比如同时写数据库的某条记录,比如多个人同时抢一张火车票等情况下。我们需要考虑进程的同步问题,以确保任一时刻只有一个进程可以拥有对资源的独占式访问。
临界区:访问临界资源的代码段(程序对资源的访问的代码只是很短的一段,但就是这一段代码引发了进程之间的竞争条件。称为关键代码段,或者临界区。所谓对进程同步,也就是确保任一时刻只有一个进程能进入关键代码段(临界区)。)
解决的方式:确保关键代码段的独占式访问是非常困难的。试图从语言本身解决并发问题,CPU利用率太低,显然是不可取的。
信号量
信号量概念的提出是并发编程领域迈出的重要一步。它一种特殊的变量,只能取自然数值并且只支持俩种操作 :等待和信号。就是我们常说的PV操作。荷兰语单词 P(passeren 传递 进入临界区) V(vrijgeven 释放 退出临界区)。
PV操作的过程
![](https://i-blog.csdnimg.cn/blog_migrate/72c1295c5e0b6d168b3655785877355f.png)
如上图所示,假设我们现在有一个信号量SV,当临界区可以进入时,SV的值为1,进程A和B都有机会进入临界区。
如果此时,进程A执行了P(SV)操作将SV减1.则进程B若再执行P(SV)操作就会被挂起。直到进程A离开临界区,并执行V(SV)操作将SV加1,临界区才重新可进入。如果此时进程B因为等待SV而处于挂起状态,则将它被唤醒,并进入临界区。
Linux下相关系统调用
头文件 #include <sys/sem.h>
函数 semget semop semctl
Semget
semget系统调用 创建一个新的信号量集,或者获取一个已经存在的信号量集。
int semget(key_t key,int num_sems,int sem_flags);
key参数是一个键值,用来标识一个全局唯一的信号量集。就像文件名全局唯一标识一个文件一样。想要通过信号量通信的进程需要使用相同的键值来创建/获取该信号量。
num_sems指定要创建/获取的信号量的数目。创建必须指定,获取可以设置为0
sem_flag时一组标志,可以使用open的mode参数。此外一般我们使用IPC_CREAT或操作,此时即使信号量已经存在,semget也不会产生错误。
成功返回一个正整数,标识符,失败返回-1。
创建信号量集,与之关联的内核数据结构体semid_ds将被创建并且初始化。
![](https://i-blog.csdnimg.cn/blog_migrate/ed1f01bffe5c0f4f7fa374ed1a32d4fe.png)
Semop系统调用
信号量关联的重要的内核变量
![](https://i-blog.csdnimg.cn/blog_migrate/3c76c1281b8559db17e402e3ae57cbea.png)
semop对信号量的操作实际上就是对这些内核变量的操作。
int semop(int sem_id,struct sembuf* sem_ops,size_t num_sem_ops);
sem_id参数是由semget调用返回的信号量集标识符,用于指定被操作的目标信号量集。
sem_ops参数指向一个sembuf结构体类型的数组,sembuf结构体的定义如下:
num_sem_ops指定执行的操作个数,即sem_ops数组中元素的个数。semop对数组sem_ops中的每个成员按照数组顺序依次执行操作,并且该过程是原子操作,以避免别的进程在同一时刻按照不同的顺序对该信号集中的信号量执行semop操作导致的竞争条件。
semop成功返回0,失败则返回-1。
semctl系统调用
int semctl (int sem_id,int sem_num,int command,...);
前俩个参数和前面一样。最后一个参数指定要执行的命令。,第四个参数的类型由用户自己定义,但sys/sem.h头文件给出了它的推荐格式