在实现多线程互斥访问共享变量中,除了使用互斥锁之外,还可以使用信号量,当然信号量不仅仅能够实现互斥,还能够实现同步问题。
信号量的历史这里就不写了,信号量的分类也有几种,这里仅仅讨论一下记录型信号量。
记录型信号量是一个与队列有关的整型变量,大白话来讲,记录型信号量是一种数据结构,除了一个整型变量外,还有一个队列,记录型信号量定义如下:
type semaphore=record
value:integer; # 整形变量
Q: list of process; # 队列
end
记录型信号量仅仅能够被P原语和V原语进行操作,P原语申请资源,V原语释放资源,那P原语和V原语是如何实现原子性操作的?
在深入探索P原语和V原语之前,我们知道了可以利用Test and Set指令(swap指令)实现互斥锁的acquire方法(点这里):
# Test and Set(python),原子性操作
def TS(lock):
if lock == 0:
lock = 1
return True
return False
# 利用TS指令实现acquire方法
def acquire():
while not TS(lock):
pass // 忙等状态
可以看到,在锁处于open状态时,TS指令保证了只能有一个线程能够改变锁的状态,使锁处于locked状态,在由locked改变为open状态之前,其他来获取锁的线程只能while循环,不能够获得锁。
接下来我们看一看记录型信号量P原语与V原语的伪代码:
# 定义记录型信号量
type semaphore=record
value:integer; # 整形变量
T: list of thread; # 队列
end
# P原语
P(s):
s.value = s.value - 1 #
if s.value < 0
then begin
block t;
insert t into T
end;
# V原语
V(s):
s.value = s.value + 1
if s.value <= 0
then begin
wakeup the first T;
remove the P from T
end
为了能够更直观的理解P操作和V操作的实现逻辑,可以将上述代码转换为下述流程图:
![ed25cefdbfa53b5ecbed9306ecb41971.png](https://i-blog.csdnimg.cn/blog_migrate/7680bec06859274521bfb311b03eecc7.png)
由上述流程图,我们可以看到
信号量的P原语,需要完成两件事:
- 当访问value时,将value减1
- 当资源被消耗完时,需要将请求资源的当前线程推入阻塞队列
信号量的V原语,需要完成两件事:
- 当访问value时,需要将value加1
- 当阻塞队列中有阻塞线程时,需要唤醒一个线程
接下来就是重点了,如何利用操作系统的TS指令实现信号量P原语、V原语,使这两个原语具有原子性?
我们可以看到,在使用信号量的时候,value作为共享资源,那就会出现如下几种争抢临界资源的情况:
- 多个线程的P原语之间
- 多个线程之间的P、V原语
- 多个线程之间的V原语
所以在P、V原语内要有互斥控制,保护临界资源不被同时访问到,避免数据不一致的情况,这里需要一个变量来保护临界资源。定义一个mutex,用来保护value,因为有一把互斥锁,获取时都可能会产生忙等现象,除了上述提到的队列T,应该还有一个队列M。
所以现在信号量的内部结构变为:
type semaphore=record
value:integer
T:list of Thread wait for value
M:list of Thread wait for internal mutex
mutex:boolean = 0 # 互斥锁
P原语:
def acquire():
while not TS(lock): # 获取mutex,用来互斥访问s.value
block t
insert t into M
s.value = s.value - 1
if s.value < 0: # 表示资源已经用完了
lock = 0 # 释放互斥锁,其他的线程或者acquire方法或者release方法可以来访问value了
wakup the first t from M # 唤醒M队列中的线程
remove the t from M
block t
insert t into T
else:
lock = 0
wakeup the first t from M
remove the t from M
V原语:
def release():
while not TS(lock):
block t
insert t into M
s.value = s.value + 1
if s.value <= 0: # 表明阻塞队列中还有待唤醒的线程
wakeup the first t from T
remove t from T
lock = 0 # 释放锁
wakeup the first t from M
remove the t from M
以上就是个人对记录型信号量的理解,以及尝试结合系统的TS指令实现了一下信号量的P原语与V原语,如果您看到了,有理解的不对的地方,欢迎指正,也欢迎与各位进行交流。
参考:
- http://faculty.salina.k-state.edu/tim/ossg/IPC_sync/ts.html#implementing-semaphores-with-test-and-set
- 图书/计算机操作系统原理与设计
- https://www.icourse163.org/course/UESTC-1205790811
- https://zhuanlan.zhihu.com/p/136173505