信号量也叫信号灯,其本质就是一个计数器,描述临界资源的数目大小。(最多能有多少资源分配给线程)。
目录
一、什么是信号量?
电影院预售1000张票,一旦预定成功,就会有你的位置,其他人无法占用;每有一个人退票或者看完电影,这张票就返还给电影院。
现在这1000张票就是一个临界资源 tickets,我们把这个临界资源划分成为一个个小资源,分配给线程,这样可以允许多个线程同时访问临界资源,从而实现并发。
信号量是一个计数器,这个计数器被合理使用,能够达到对临界资源预定的目的。每个线程要想访问临界资源,都得申请信号量!
二、信号量申请和释放的过程(PV操作)
其实信号量的申请和释放过程,和之前说线程资源冲突的场景有点类似。假设tickets = 1000,最基本的理解如下:
申请信号量 ==》 tickets --
释放信号量 ==》 tickets ++
但是由于 ticket-- 或者 tickets ++这个操作不是原子的,会被其他线程“乱入”,所以我们要对其进行加锁,等到自增或者自减结束以后,我们再解锁。这就是典型的PV操作
tickets = 0,此时说明票已经全部售出了,如果还有线程要来申请,那就只能让这些线程暂时挂起,等其他线程释放信号量以后,再给唤醒挂起的线程。
三、信号量操作函数
Linux专门为信号量提供一种数据结构,和之前学的信号集一样
sem_t sem; //创建一个信号量
信号量分为有名信号量、无名信号量、System V信号量,有名信号量和无名信号量只是初始化和销毁的方式不同,他们对信号量的申请和释放方式是一样的。
1、有名信号量的初始化和销毁
有名信号灯其实就是一个文件,既然是文件就有文件的打开、关闭、删除,这就对应了有名信号量的三个操作函数。
(1) 有名信号量的初始化 sem_open
sem_open 的作用是创建或者打开一个信号量文件。
- 如果信号量文件不存在,那就会给该信号量文件命名并设置权限(创建好的信号量文件会被放到 /dev/shm 目录下)
- 如果信号量文件已存在,那就直接打开
第一个参数 name:给信号量起的名字
第二个参数oflag:打开方式。常用可选值如下
可选值 | 含义 |
O_CREAT | 如果信号量不存在,那就创建,返回创建好的信号量的地址 如果信号量已存在,那就返回已有的信号量的地址 |
第三个参数 mode:设置文件权限。如果信号量已经存在,第三和第四个参数都会被忽略。
第四个参数 value:信号量的初始值,即资源数目。如果信号量已经存在,第三和第四个参数都会被忽略。
返回值:失败返回 SEM_FAILED;成功时,如果信号量不存在,则返回创建好的信号量的地址,如果不存在,则返回已有信号量的地址。
(2) 有名信号量的关闭 sem_close
sem_close 函数的作用是关闭一个已经打开的有名信号量。
(3) 有名信号量的删除 sem_unlink
sem_unlink 的作用是删除一个已有的有名信号量。
2、无名信号量的初始化和销毁
(1) 无名信号量初始化 sem_init
创建好的信号量只是一个空壳,初始值是多少,即要出售多少张票,我们并不知道,因此需要对信号量进行初始化。下面就依次介绍sem_init的各个参数。
第一个参数,信号量的地址,即要初始化哪个信号量。
第二个参数,选择 线程间共享这个信号量 or 进程间共享这个信号量。如果 pshared = 0,线程间共享;pshared = 1,进程间共享。
第三个参数,设置信号量的初始值。
返回值:成功返回0,失败返回-1
(2) 无名信号量的销毁 sem_destroy
调用以后,信号量这个计数器会被销毁。参数是 要销毁的信号量的地址。成功返回0,失败返回-1。
3、有名 / 无名信号量的申请和释放
(1) 申请信号量 sem_wait
你可以将上述信号量申请过程当作这个函数的底层实现。参数是 要申请的信号量的地址。成功返回0,失败返回-1。
(2) 释放信号量 sem_post
这里的释放信号量和下面的销毁不一样,这里的释放只是把申请到的信号量归还给临界资源,而销毁是将信号量这个计数器给销毁。此时计数器的值 +1。参数是 要释放的信号量的地址。成功返回0,失败返回-1。