信号和解锁订单

时间:2011-06-21 00:35:36

标签: c++ pthreads

void WorkHandler::addWork(Work* w){
    printf("WorkHandler::insertWork Thread, insertWork locking \n");
    lock();
    printf("WorkHandler::insertWork Locked, and inserting into queue \n");
    m_workQueue.push(w);
    signal();
    unLock();
}

我按照教程进行了操作。我想知道是否可以像这样改变singal()和unLock()的顺序

void WorkHandler::addWork(Work* w){
    printf("WorkHandler::insertWork Thread, insertWork locking \n");
    lock();
    printf("WorkHandler::insertWork Locked, and inserting into queue \n");
    m_workQueue.push(w);
    unLock();
    signal();
}

如果我不能这样做,请你详细说明为什么我不允许这样做? 提前谢谢。

3 个答案:

答案 0 :(得分:22)

首先,这里没有正确性问题。任何订单都可以。回想一下,无论何时使用条件变量,都必须在等待时循环一个谓词:

pthread_mutex_lock(mutex);
while (!predicate)
  pthread_cond_wait(cvar);
pthread_mutex_unlock(mutex);

通过解锁后发出信号,您不会引入任何正确性问题;线程仍然可以保证唤醒,最糟糕的情况是第一次唤醒 - 此时它会看到谓词变为真,然后继续。

但是,可能会出现两个可能出现的性能问题。

  • “快点等一下”。基本上,如果在保持锁定时发出信号,则另一个线程仍需要等到互斥锁可用。许多pthreads实现将而不是唤醒另一个线程,只需将其移动到互斥锁的等待队列,从而节省不必要的唤醒 - >等待周期。在某些情况下,这是未实现或不可用的,导致潜在的虚假上下文切换或IPI。
  • 虚假的唤醒。如果您在解锁后发出信号,则另一个线程可能会发出另一个唤醒。请考虑以下情形:

    1. 线程A开始等待将项添加到线程安全队列。
    2. 线程B在队列中插入一个项目。解锁队列后,但在发出信号之前,会发生上下文切换。
    3. 线程C在队列中插入一个项目,并发出cvar信号。
    4. 线程A醒来,并处理这两个项目。然后它回到等待队列。
    5. 线程B恢复,并向cvar发出信号。
    6. 线程A醒来,然后立即返回睡眠状态,因为队列为空。
    7. 如您所见,这可能会引入虚假的唤醒,这可能会浪费一些CPU时间。

就个人而言,我认为无论如何都不值得担心。您不经常知道您的实现是否支持将服务器从条件变量移动到互斥等待队列,这是您可以用来决定使用哪个的真正标准。

我的直觉是,如果我必须选择,解锁后发出的信号略微不太可能引入低效率,因为效率低下需要三线竞赛,而不是两个 - “快点等待”条件的线程竞赛。但是,这并不值得担心,除非基准测试显示过多的上下文切换开销等。

答案 1 :(得分:2)

您的问题的答案是“是”。事实上,它稍微好一点(正如你可能已经猜到的那样),因为它避免了“快点等待”的问题,即唤醒线程测试条件只是为了让它立即阻塞它需要在测试之前获取的互斥锁条件。

这个答案取决于这些事情的猜测:

  • lockpthread_mutex_lock的精简包装。
  • unLockpthread_mutex_unlock的精简包装。
  • signalpthread_cond_signal的包装器。
  • 您锁定和解锁的互斥锁是您向pthread_cond_wait提供的互锁锁。

答案 2 :(得分:1)

这篇文章非常值得您阅读:

Signal with mutexed or not?

假设您使用与条件变量相同的互斥锁将条件更改为原子。有两种情况你应该知道他们的行为:

  1. 在持有互斥锁的同时等待信号(条件变量)。结果是让线程加入条件var的队列,然后进入休眠状态。
  2. 发出信号,但没有互斥。对于这种情况,线程不会睡觉但会阻塞它。 (我在这个问题上犯了一个错误,我认为它也会睡觉。在这种情况下,如果生成器信号和上下文切换在它释放互斥锁之前发生,那么所有线程都会唤醒并知道它们无法锁定互斥锁,永远地去睡觉。这是错误的,因为他们不会睡觉而是等待和阻止。)
  3. Pthreads通过 wait-morphing 实现,也就是说,它不是在发出信号时唤醒线程,而只是将条件变量上的线程传递给附加的互斥队列。因此,在没有太多性能影响的情况下,锁定信号更为可取。

    对于解锁互斥锁之前的信令,可能会导致虚假唤醒。如果您的代码没有很好地设计来处理虚假唤醒所做的谓词更改,那么您应该在按住锁定时选择信号。