module Condition:sig
..end
条件变量。
当多个线程希望访问由互斥锁(互斥锁)保护的共享数据结构时,条件变量非常有用。
条件变量是一种通信通道。在接收端,一个或多个线程可以表明它们希望等待某个属性变为真。在发送端,一个线程可以发出信号,表示该属性已变为真,导致一个(或多个)等待线程被唤醒。
例如,在队列数据结构的实现中,如果一个希望提取元素的线程发现队列当前为空,则该线程等待队列变为非空。将元素插入队列的线程会发出信号,表明队列已变为非空。为此目的使用条件变量。此通信通道传达信息,表明属性“队列非空”为真,或者更准确地说,可能是真。(我们将在下面解释为什么信号的接收者不能确定该属性是否成立。)
继续以队列为例,假设队列具有固定的最大容量,那么希望插入元素的线程可能会发现队列已满。然后,该线程必须等待队列变为未满,而从队列中提取元素的线程会发出信号,表明队列已变为未满。为此目的使用另一个条件变量。
简而言之,条件变量c
用于传达关于由互斥锁m
保护的共享数据结构D的某个属性P可能为真的信息。
条件变量提供了一种比忙等待更有效的替代方案。当希望等待属性P变为真时,而不是编写忙等待循环
Mutex.lock m;
while not P do
Mutex.unlock m; Mutex.lock m
done;
<update the data structure>;
Mutex.unlock m
在循环体中使用Condition.wait
,如下所示
Mutex.lock m;
while not P do
Condition.wait c m
done;
<update the data structure>;
Mutex.unlock m
忙等待循环效率低下,因为等待线程会消耗处理时间并创建互斥锁m
的竞争。调用Condition.wait
允许等待线程被挂起,因此在等待期间它不会消耗任何计算资源。
使用条件变量c
,恰好关联一个互斥锁m
。此关联是隐式的:互斥锁m
不会作为参数显式传递给Condition.create
。程序员需要知道每个条件变量c
关联的互斥锁m
。
使用互斥锁m
,可以关联多个条件变量。在有界队列的示例中,一个条件变量用于指示队列非空,另一个条件变量用于指示队列未满。
使用条件变量c
,应该恰好关联一个逻辑属性P。此类属性的示例包括“队列非空”和“队列未满”。程序员需要跟踪每个条件变量对应的属性P。在条件变量c
上发送信号,表示属性P为真或可能为真。但是,在接收端,被唤醒的线程不能假设P为真;在调用Condition.wait
终止后,必须显式测试P是否为真。出现这种情况有几个原因。一个原因是,在发送信号的时刻和等待线程接收信号并被调度的时刻之间,属性P可能会被能够获取互斥锁m
并更改数据结构D的其他线程所取缔。另一个原因是可能会发生虚假唤醒:即使没有发送信号,等待线程也可能被唤醒。
这是一个完整的示例,其中互斥锁保护一个顺序无界队列,并且使用条件变量来发出信号表示队列非空。
type 'a safe_queue =
{ queue : 'a Queue.t; mutex : Mutex.t; nonempty : Condition.t }
let create () =
{ queue = Queue.create(); mutex = Mutex.create();
nonempty = Condition.create() }
let add v q =
Mutex.lock q.mutex;
let was_empty = Queue.is_empty q.queue in
Queue.add v q.queue;
if was_empty then Condition.broadcast q.nonempty;
Mutex.unlock q.mutex
let take q =
Mutex.lock q.mutex;
while Queue.is_empty q.queue do Condition.wait q.nonempty q.mutex done;
let v = Queue.take q.queue in (* cannot fail since queue is nonempty *)
Mutex.unlock q.mutex;
v
由于对Condition.broadcast
的调用发生在临界区内,因此只要互斥锁被解锁,以下属性就成立:如果队列非空,则没有线程正在等待,或者换句话说,如果某个线程正在等待,则队列必须为空。这是一个理想的属性:如果尝试执行take
操作的线程即使在队列非空的情况下仍可能保持挂起状态,则这将是一个有问题的状况,称为死锁。
type
t
条件变量的类型。
val create : unit -> t
create()
创建并返回一个新的条件变量。此条件变量应与(在程序员的脑海中)某个互斥锁m
以及由互斥锁m
保护的数据结构的某个属性P相关联。
val wait : t -> Mutex.t -> unit
仅当m
是与条件变量c
关联的互斥锁,并且仅当m
当前被锁定时,才允许调用wait c m
。此调用会以原子方式解锁互斥锁m
并在条件变量c
上挂起当前线程。稍后,在通过Condition.signal
或Condition.broadcast
发出条件变量c
的信号后,此线程可能会被唤醒;但是,它也可能无缘无故被唤醒。wait
返回之前,会再次锁定互斥锁m
。当wait
返回时,不能假设与条件变量c
关联的属性P成立;必须在调用wait
后显式测试P是否成立。
val signal : t -> unit
signal c
唤醒一个在条件变量c
上等待的线程(如果存在)。如果不存在,则此调用无效。
建议在临界区内调用signal c
,即在与c
关联的互斥锁m
被锁定的同时。
val broadcast : t -> unit
broadcast c
唤醒所有在条件变量c
上等待的线程。如果不存在,则此调用无效。
建议在临界区内调用broadcast c
,即在与c
关联的互斥锁m
被锁定的同时。