等待函数究竟是如何工作的(关于条件变量)

时间:2017-12-14 01:45:56

标签: c multithreading

我对条件变量如何工作(关于共享数据并发访问)的理解有点困惑

以下是用于说明我当前问题的伪C代码

// Thread 1: Producer
void cakeMaker()
{
    lock(some_lock);
    while(number_of_cakes == MAX_CAKES)
        wait(rack_has_space);

    number_of_cakes++;

    signal(rack_has_cakes);
    unlock(some_lock);
}

// Thread 2: Consumer
void cakeEater()
{
    lock(some_lock);
    while(number_of_cakes == 0)
        wait(rack_has_cakes);

    number_of_cakes--;

    signal(rack_has_space);
    unlock(some_lock);
}

假设我们目前有number_of_cakes = 0,因此Thread 2目前停留在wait(rack_has_cakes)Thread 1运行并将number_of_cakes增加1.然后它会调用signal(rack_has_cakes) - 这会唤醒Thread 2,不幸的是Thread 2Thread 1调用unlock(some_lock)之前醒来{1}},所以它再次回到睡眠状态并且错过了信号。

我对waitsignal的内部运作感到非常困惑?他们究竟发生了什么?它们是否像我们调用signal时自动设置为1的布尔标志,当等待成功时再次设置为0?发生了什么事?

有人可以让我逐步完成上述代码的迭代,重点强调信号和等待期间发生的事情吗?

2 个答案:

答案 0 :(得分:4)

  

线程2在线程1调用unlock(some_lock)之前唤醒,所以它会继续   再次回到睡眠状态并且错过了信号。

不,那不是它的运作方式。我将使用C ++ std::condition_variable作为我的引用,但POSIX线程,以及大多数普通的互斥和条件变量实现都以相同的方式工作。基本概念是相同的。

线程2在开始等待条件变量时锁定了互斥锁。 wait()操作会解锁互斥锁和waits on the condition variable atomically

  

以原子方式释放锁,阻止当前正在执行的线程,以及   将它添加到等待* this的线程列表中。

此操作被视为" atomic&#34 ;;换句话说,不可分割。

然后,当发出条件变量信号时,线程重新锁定互斥锁:

  

取消阻止时,无论原因如何,都会重新获取锁定并等待   退出。

线程没有"回去睡觉"在另一个线程"调用解锁"之前。如果互斥锁尚未解锁:当线程在被条件变量发出信号时唤醒时,线程将一直等到成功再次锁定互斥锁。这是无条件的。当wait()返回时,互斥锁仍然被锁定。然后,只有这样,wait()函数才会返回。所以,事件的顺序是:

  1. 一个线程锁定互斥锁,将一些计数器,变量或任何类型的互斥锁保护数据设置为另一个线程正在等待的状态。执行此操作后,线程会发出条件变量信号,然后在闲暇时解锁互斥锁。

  2. 另一个线程在条件变量wait()之前锁定了互斥锁。 wait()的先决条件之一是必须在wait()链接条件变量之前锁定互斥锁。因此,wait()操作以原子方式解锁互斥锁"。也就是说,当互斥锁被解锁时,没有实例,并且线程还没有等待条件变量。当wait()解锁互斥锁时,您可以保证线程将等待,并且它将被唤醒。你可以把它带到银行。

  3. 一旦发出条件变量信号,wait()线程不会wait()返回,直到它可以重新锁定互斥锁。从条件变量接收到信号只是第一步,必须在wait()操作的最后一步中通过线程再次锁定互斥锁。当然,这只发生在信令线程解锁互斥锁之后。

  4. 当线程通过条件变量发出信号时,wait()返回。但不是立即,它必须等到线程再次锁定互斥锁,无论多长时间。它不会回到睡眠状态,但要等到它再次锁定互斥锁,然后再返回。保证接收到的条件变量信号将导致线程从wait()返回,并且线程将重新锁定互斥锁。并且由于原始的解锁 - 然后等待操作是原子的,因此可以保证收到条件变量信号。

答案 1 :(得分:0)

<块引用>

假设我们当前有 number_of_cakes = 0,所以线程 2 当前卡在 wait(rack_has_cakes) 上。线程 1 运行并将 number_of_cakes 增加 1。然后它调用 signal(rack_has_cakes) - 这会唤醒线程 2,不幸的是线程 2 在线程 1 调用 unlock(some_lock) 之前被唤醒,因此它再次回到睡眠状态并且信号已错过。

你是对的,这可能会发生,因为你的信号命令顺序不正确。 在生产者和消费者中,您都设置了以下命令顺序:

signal(rack_has_cakes);
unlock(some_lock);

但顺序应该是:

unlock(some_lock);
signal(rack_has_cakes);

您首先必须解锁互斥锁,然后向另一个线程发送信号。 由于signal命令是条件变量wait(),signal()命令是线程安全的,所以之前不用担心释放锁。

但这一步非常重要,因为它让另一个线程有机会锁定互斥锁。