C++ 信号和解锁命令

C++ 信号和解锁命令,c++,pthreads,C++,Pthreads,我遵循了一个教程,我得到了这个。我想知道这样更改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_wor

我遵循了一个教程,我得到了这个。我想知道这样更改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);
    signal();
    unLock();
}
如果我不能这样做,你能告诉我为什么我不能这样做的细节吗?
提前谢谢。

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

这个答案是基于以下猜测得出的:

  • lock
    pthread\u mutex\u lock
    的薄包装
  • unLock
    pthread\u mutex\u unLock
    的薄包装
  • signal
    pthread\u cond\u signal
    的东西包装器
  • 锁定和解锁的互斥锁是您给
    pthread\u cond\u wait
    的互斥锁

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

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();
}
通过在解锁后发送信号,您不会引入任何正确性问题;线程仍然保证会被唤醒,最糟糕的情况是另一个唤醒首先出现——此时它看到谓词变为true并继续

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

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

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

就我个人而言,我认为这两种情况都不值得过分担心。您通常不知道您的实现是否支持将waiter从条件变量移动到互斥等待队列,这是您可以用来决定使用哪个队列的唯一真正标准


我的直觉是,如果我必须选择的话,解锁后的信号发送不太可能导致效率低下,因为效率低下需要三个线程的竞争,而不是两个线程的“快速等待”状态。然而,这并不值得担心,除非基准测试显示了太多的上下文切换开销或其他问题。

这篇文章确实值得一读,以回答您的问题:

假设您将相同的互斥体与条件变量一起使用,以使条件更改成为原子性的。有两种情况,您应该了解他们的行为:

  • 保持互斥时等待信号(条件变量)。结果是让线程加入条件变量的队列,然后进入睡眠状态
  • 有信号但没有互斥。在这种情况下,线程不会休眠,而是阻塞它。(我犯的一个错误是,我认为它也会休眠。在这种情况下,如果生产者在释放互斥锁之前发出信号并切换上下文,那么所有线程都会醒来,知道它们无法锁定互斥锁,永远进入休眠状态。这是错误的,因为它们不会休眠,而是等待并阻塞)
  • Pthreads是通过等待变形来实现的,也就是说,它不是在发出信号时唤醒线程,而是将条件变量上的线程传输到附加的互斥队列。因此,锁定时的信号更可取,不会对性能造成太大影响


    对于解锁互斥锁之前的信令,它可能会导致虚假唤醒。如果您的代码设计得不好,无法处理虚假唤醒所做的谓词更改,则应在保持锁定的同时选择signal。

    pthread_cond_signal的手册页上说,您可以发出信号,表明您是否拥有互斥锁(基本上是在锁定和解锁之间的任何时间)可能存在的not true副本。等待线程在获取互斥锁之前无法继续,而在“信号器”解锁互斥锁之前,互斥锁无法发生。原来的顺序实际上对性能更好。(有关详细信息,请参见“重复”问题中的已接受答案。)您当然可以“袖手旁观”您的答案。但是,如果你打算推荐一个与世界上所有其他参考文献基于“性能”而推荐的序列相反的序列,我认为你有责任提供性能分析。所以我支持我的反对票。@Nemo,在某些情况下,将服务员转移到互斥对象的等待队列是不可能的。在Linux上,当condvar设置了其
    pshared
    属性时就是这种情况,例如,在发送condvar信号的过程中,互斥锁的地址可能不同或完全不可用。事实上,这是不使用pshared默认值的动机之一(另一个原因是mm private futex哈希表减少了哈希冲突的发生)@Nemo,是的,如果您知道pthreads实现支持移动waiter,那么这种方式可能会稍微好一些。另一方面,如果y,则解锁后的信号发送更好(可能幅度更大)
    pthread_mutex_lock(mutex);
    while (!predicate)
      pthread_cond_wait(cvar);
    pthread_mutex_unlock(mutex);