信号量
信号量
工作场景、解决痛点
用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。
为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。
原理
本质
信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据
两种原子操作(P、V操作)
信号量表示资源的数量,控制信号量的方式有两种原子操作:
P 操作:这个操作会把信号量减去 1
- 若相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;
- 若相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
V 操作:这个操作会把信号量加上 1
- 若相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;
- 若相加后如果信号量 > 0,则表明当前没有阻塞中的进程;
原则:P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。
两种用法及原理
信号量记数 个人吐槽:
刚开始我感觉这个计数方式有点反常识,为什么让计数值等于 使用+等待 进程数呢? 这样不就很直观了吗?为什么要取 [-n, 1]
区间这么怪呢。
主要是因为他这里可以有两种用法,该记数还要用于标识是 互斥信号量 还是 同步信号量
用法 —— 互斥信号量 (初始为1,无先后顺序)
接下来,举个例子,如果要使得两个进程互斥访问共享内存,我们可以初始化信号量为 1
。
具体的过程如下:
- 进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1,故在进程 A 执行 P 操作后信号量变为 0,表示共享资源可用,于是进程 A 就可以访问共享内存。
- 若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,这就意味着临界资源已被占用,因此进程 B 被阻塞。
- 直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0,接着就会唤醒阻塞中的线程 B,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1。
可以发现,信号初始化为 1
,就代表着是 互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。
另外,在多进程里,每个进程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个进程能密切合作,以实现一个共同的任务。
用法 —— 同步信号量 (初始为0,有前后顺序)
例如,进程 A 是负责生产数据,而进程 B 是负责读取数据,这两个进程是相互合作、相互依赖的。
进程 A 必须先生产了数据,进程 B 才能读取到数据,所以执行是有前后顺序的。
那么这时候,就可以用信号量来实现多进程同步的方式,我们可以初始化信号量为 0
。
具体过程:
- 如果进程 B 比进程 A 先执行了,那么执行到 P 操作时,由于信号量初始值为 0,故信号量会变为 -1,表示进程 A 还没生产数据,于是进程 B 就阻塞等待;
- 接着,当进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的进程 B;
- 最后,进程 B 被唤醒后,意味着进程 A 已经生产了数据,于是进程 B 就可以正常读取数据了。
可以发现,信号初始化为 0
,就代表着是 同步信号量,它可以保证进程 A 应在进程 B 之前执行。