模块 Condition

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.signalCondition.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被锁定的同时。