信令
一个线程向另一个线程发送信号,以通知它发生了某些事情。它完成了一个执行顺序的约束,解决了所谓的序列化问题。
集合点
考虑线程 A 的两个语句 A1,A2,线程 B 的两个语句 B1,B2。我们要求 A2 必须在 B1 后发生,B2 必须在 A1 后发生。换言之,A 必须等待 B,B 也必须等待 A,只有在它们都执行完步 1 后才能执行步 2。
因此,设计两个信号量 aX
表示线程 A 到达了集合点,bX
表示线程 B 到达了集合点。那么 A 发送 aX
并等待 bX
才继续执行,B 发送 bX
并等待 aX
才继续执行。
注意,发送一定要放在等待之前,否则可能会导致死锁。
互斥
考虑如何通过信号量来实现对一个变量的互斥访问。
为每个互斥变量安排一个信号量 m
,初态为 1,所有访问这个变量的代码都应该套上 m.wait()
在前而 m.signal()
在后。
多路复用
类似互斥,但允许多个线程同时在临界区内执行,并强制给定一个并发线程数的上限。
为每个多路复用区安排一个信号量 m
,初态为其数量限制,所有访问这临界区的代码都应该套上 m.wait()
在前而 m.signal()
在后。
屏障
我们从集合点模式出发做一些推广。现在有很多个线程,每个线程都执行两个步骤 rendezvous 和 critical,我们需要保证只要有任何一个线程未完成第一步,就不存在任何一个线程进入第二步。
这里我们需要两个部分,一个自带互斥锁的计数器 count
和一个标识屏障是否被推倒的信号量 bar
。
思路非常清晰,每个线程执行完 rendezvous 后修改计数器,如果计数器等于 n 则推倒屏障。如果只推倒一次是有问题的。两种解决方案:要么连续推倒 n 次,要么在每个线程等待屏障等到后再次推倒屏障。
rendezvous
m.wait()
count++
m.signal()
if count==n: bar.signal()
bar.wait()
bar.signal()
critical
这里出现了一种新模式:快速连续的 wait 和 signal,我们将它称作旋转门。它一次只允许一个线程通过。它可以锁定以组织所有线程。
在本例中,初态下旋转门被锁定。第 n 个到达的线程将解锁它,然后 n 个线程将都可以通过。
可重用屏障
现在我们希望在所有线程都通过屏障后,再次锁定旋转门。
考虑到代码可能会循环执行,为了防止某些 HKJ 线程搞事情,我们设置两个旋转门 ts1,ts2
。最初第一个被锁定,第二个被打开。当所有线程到达第一个时,我们锁定第二个并解锁第一个。对第二个也同理。
rendezvous
m.wait()
cnt++
if cnt==n: ts2.wait(), ts1.signal()
m.signal()
ts1.wait()
ts1.signal()
critical
m.wait()
cnt--
if cnt==0: ts1.wait(), ts2.signal()
m.signal()
ts2.wait()
ts2.signal()
另一个方案是连续推倒 n 次。
rendezvous
m.wait()
cnt++
if cnt==n: ts1.signal(n)
m.signal()
ts1.wait()
critical
m.wait()
cnt--
if cnt==0: ts2.signal(n)
m.signal()
ts2.wait()
我们以后可以将这种屏障模式描述为一个固定的类封装形式。
bar=Barrier(n)
bar.wait()
队列
这里说的队列是这样的:领袖和粉丝配对跳舞。当领袖到达时,会检查是否有粉丝在等待。如果有,他们就结伴然后干一些什么事,没有的话就抽根烟等一等。
我们需要两个信号量 lQ
和 fQ
来标识领袖等待的数量和粉丝等待的数量。这里似乎只是一个可以重叠的集合点,因此对领袖而言,我们有
fQ.signal()
lQ.wait()
dance()
独占队列
现在我们在队列问题的基础上,增加一个约束条件,每个领袖只能与一个粉丝同时调用 dance,换言之这个 dance 的调用必须由领袖和粉丝不断地交替进行。
对于领袖而言
m.wait()
if fCnt>0: fCnt--, fQue.signal()
else: lCnt++, m.signal(), lQue.wait()
dance()
rendezvous.wait()
m.signal()
对于粉丝而言
m.wait()
if lCnt>0: lCnt--, lQue.signal()
else: fCnt++, m.signal(), fQue.wait()
dance()
rendezvous.signal()
当一个领袖到达时,如果已经有等待的粉丝就唤醒他并开始跳舞,否则进入等待。最后的 rendezvous
是为了保证在本次配对结束前,不能有新的人打开互斥锁。