【问题标题】:Boost::Thread / C++11 std::thread, want to wake worker thread on conditionBoost::Thread / C++11 std::thread,想在条件下唤醒工作线程
【发布时间】:2013-11-25 08:00:03
【问题描述】:

我使用 Boost::thread 作为工作线程。我想在没有工作要做的时候让工作线程进入睡眠状态,一旦有工作要做就唤醒它。我有两个保存整数的变量。当整数相等时,没有工作要做。当整数不同时,有工作要做。我当前的代码如下所示:

int a;
int b;

void worker_thread()
{
    while(true) {
        if(a != b) {
            //...do something
        }
                    //if a == b, then just waste CPU cycles
    }
}

//other code running in the main thread that changes the values of a and b

我尝试使用条件变量并在 a == b 时让工作线程进入睡眠状态。问题是存在竞争条件。这是一个示例情况:

  1. 工作线程评估 if(a == b),发现它为真。
  2. 主线程更改 a 和/或 b 使得它们不再相等。在工作线程上调用 notify_one()。
  3. 工作线程忽略 notify_one(),因为它仍然处于唤醒状态。
  4. 工作线程进入睡眠状态。 死锁

如果我可以避免条件变量会更好,因为我实际上不需要锁定任何东西。但是只要让工作线程在 a == b 时进入睡眠状态,并在 a != b 时唤醒。有没有办法做到这一点?

【问题讨论】:

  • “因为我实际上不需要锁定任何东西”......我相信这被称为“乐观并发”。

标签: c++ multithreading boost c++11


【解决方案1】:

您似乎没有正确同步访问:当您在工作线程中读取ab 时,至少在访问与生产者共享的值时需要获取锁:因为工作线程持有锁,主线程不能更改ab。如果它们不相等,工作线程可以释放锁并开始处理这些值。如果它们相等,则工作线程代替条件变量上的wait()s 持有锁!条件变量的主要功能是原子地释放锁并进入睡眠状态。

当主线程更新a 和/或b 时,它会获取锁,进行更改,释放锁并通知工作线程。工作线程显然没有持有锁,而是在下一次检查到期时或作为通知的结果获取它,检查值的状态并wait()s 或处理这些值。

如果操作正确,就不会出现竞争条件!

我错过了您的关键混淆:“因为我实际上不需要锁定任何东西”!好吧,当您有两个线程可以同时访问相同的值并且至少其中一个正在修改该值时,如果没有同步,您就会发生数据竞争。任何具有数据竞争的程序都有未定义的行为。换句话说:即使您只想将bool 值从一个线程发送到另一个线程,您也确实需要同步。同步不必采用锁的形式(例如,可以使用原子变量同步值),但是进行非平凡的通信,例如,涉及两个 ints 而不仅仅是一个具有原子的通信通常是相当困难的!您几乎可以肯定想要使用锁。然而,你可能还没有发现这种深切的渴望。

【讨论】:

  • 我的程序结构是(据我所知)没有争用。我上面所说的部分内容是不准确的......我只是想简化问题陈述。发生的情况是工作线程只修改了一个变量。主线程只修改另一个变量。但是两个线程都读取了这两个变量。如果读取了一个过时的值,没问题,主线程要么再次执行一次while循环并等待接收更新的值,要么工作线程将只做部分工作,其余的将在下一次迭代中完成。 ...续
  • (接上面的评论)虽然我假设在任何时候,我正在读取的整数(实际上是指针)要么是旧值,要么是新值。例如,假设一个线程在另一个线程尝试读取整数的同时尝试更新整数。尝试读取它的线程获取旧值或新值是可以的,但如果它获取其他值,程序将中断。根据我的阅读,对于整数或指针数据类型,这不会发生在我的体系结构 (X86) 上。
  • @486DX2-66:让我用简单的话来说:如果一个线程写入一个值,另一个线程读取一个值,并且一个或两个线程缺少同步,那么您有未定义的行为!也就是说,没有同步程序是错误的。该标准的相关引用是 1.10 [intro.multithread] 第 21 段:“如果程序的执行包含不同线程中的两个冲突操作,则程序的执行包含数据竞争,其中至少一个不是原子的,并且在其他。任何此类数据竞争都会导致未定义的行为。..."
  • @486DX2-66:所指的“发生在之前”的关系取决于某种程度的同步,也在 1.10 [intro.multithread] 中进行了描述。该部分有些重要,但定义当然不取决于时间,而是取决于同步原语。
【解决方案2】:

需要考虑的事情:

  1. 您的线程是否有任何理由保持睡眠状态?
  2. 为什么不启动一个新线程并在它完成工作后让它自然死亡?
  3. 如果在任何时间点只有一个代码路径处于活动状态(所有其他线程都处于休眠状态),那么您的设计不允许并发。

最后,如果你使用线程间共享的变量,你应该使用atomics。这将确保对您的值的访问是同步的。

【讨论】:

  • Atomics 通常仅供专家使用,因为许多程序的锁更简单,也更容易正确。如果您需要等待某个条件为真,那么在原子上旋转是在浪费循环。
  • 我正在尝试构建一个包含一个生产者和一个消费者的缓冲日志流。它必须是 FIFO,我想避免为每个传入消息启动一个新线程的开销。此外,如果我使用不同的线程来处理每条消息,我将无法控制消息的顺序——较早的消息可能会在稍后到达的消息之后完成。我的目标不是增加吞吐量(如果不改变硬件就无法做到这一点),而是减少延迟。基本上,当消息从应用程序到达时,主线程将其复制到缓冲区中。然后工作线程将其打印到文件中。
猜你喜欢
  • 1970-01-01
  • 2013-11-26
  • 2015-11-20
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 1970-01-01
  • 1970-01-01
  • 2020-12-21
相关资源
最近更新 更多