【问题标题】:C++ condition_variable wait_for returns instantlyC++ condition_variable wait_for 立即返回
【发布时间】:2017-05-14 15:53:45
【问题描述】:

这是来自http://www.cplusplus.com/reference/condition_variable/condition_variable/wait_for/的简单代码

如果我在启动线程中注释行,为什么 wait_for() 会立即返回?

像这样:

// condition_variable::wait_for example
#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>             // std::chrono::seconds
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status

std::condition_variable cv;

int value;

void read_value() {
  std::cin >> value;
  cv.notify_one();
}

int main ()
{
  std::cout << "Please, enter an integer (I'll be printing dots): ";
  //std::thread th (read_value);

  std::mutex mtx;
  std::unique_lock<std::mutex> lck(mtx);
  while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout) {
    std::cout << '.';
  }
  std::cout << "You entered: " << value << '\n';

  //th.join();

  return 0;
}

更新:

请不要在这个例子中寻找其他问题(相关的缓冲cout ...)。最初的问题是关于为什么会跳过 wait_for。

【问题讨论】:

  • 它似乎不会自动加载 pthread lib,在启动程序之前使用 LD_PRELOAD=/lib/x86_64-linux-gnu/libpthread.so.0 可以正常工作。有什么建议,为什么会这样?
  • 显示用于构建的命令行。显示运行的输出。

标签: c++ c linux c++11 pthreads


【解决方案1】:

简短回答:使用-pthread 编译,您的问题就会消失。

更新:

This is a confirmed bug/issue in libstdc++. 如果没有将-pthread 作为编译器标志传入,则定时等待调用将立即返回。鉴于该问题的历史(3 年),它不太可能很快得到解决。无论如何,请阅读下面我的消息,了解为什么您应该使用带有谓词的条件变量来避免虚假唤醒问题。即使您与 posix 线程库链接,它仍然适用。

cplusplus.com 上的示例代码有几个问题。首先,修改这一行:

std::cout << '.';

变成这样:

std::cout << '.';
std::cout.flush()

否则,如果标准输出没有被刷新,您将看不到任何点。

如果你像这样编译你的程序(带有注释掉的线程):

g++ yourcode.cpp -std=c++11

然后生成的 a.out 程序会在未使用线程时出现您描述的问题。也就是说,当线程没有被使用时,会有一个虚假的唤醒。就好像在某个未知来源的条件变量上调用了一个幻象 notify() 调用。这很奇怪,但并非不可能。

但是一旦你取消注释掉线程变量的声明,程序就会因为程序没有使用多线程而抛出异常(并崩溃):

terminate called after throwing an instance of 'std::system_error'
  what():  Enable multithreading to use std::thread: Operation not permitted
Please, enter an integer (I'll be printing dots): Aborted (core dumped)

有趣,所以让我们用-pthread重新编译来解决这个问题

g++ yourcode.cpp -std=c++11 -pthread

现在无论有没有线程,一切都按预期工作。似乎不再有虚假的唤醒。

现在让我们谈谈为什么您会看到您所看到的行为。 应始终编写使用条件变量的程序来处理虚假唤醒。最好使用谓词语句。也就是说,您可能会收到一个幻像通知,导致您的 wait 或 wait_for 语句提前返回。 cplusplus.com 网站上的示例代码没有使用谓词,也没有处理这种可能性。

我们修改如下:

更改此代码块:

  while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout) {
    std::cout << '.';
  }

变成这样:

 while (cv.wait_for(lck,std::chrono::seconds(1), condition_check)==false) {
   std::cout << '.';
   std::cout.flush();
 }

然后在main之外的其他地方,但是在value的声明之后,添加这个函数:

bool condition_check() {
   return (value != 0);
}

现在等待循环将每秒和/或在输入线程进行notify 调用时唤醒。等待循环将一直持续到value != 0。 (从技术上讲,value 应该在线程之间同步,无论是使用锁还是作为 std::atomic 值,但这是一个小细节)。

现在的谜团是为什么wait_for 的非谓词版本会出现虚假的wake_up 问题。我的猜测是,当使用多线程运行时 (-pthread) 时,单线程 C++ 运行时会出现问题。可能condition_variable在链接posix线程库时有不同的行为或不同的实现。

【讨论】:

  • 即使我添加 -lpthread 也会出现同样的问题。即使我添加 -lpthread,链接器也不会链接 pthread 库。但是,如果我使用 LD_PRELOAD pthread 运行程序,它会按预期工作。似乎这是与我的工具链版本/配置相关的问题。在具有相同工具链版本的两台机器上测试。 (g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609)
【解决方案2】:

这段代码有几个问题:

首先,正如您所注意到的,必须使用-pthread 选项构建程序。

其次,如果要查看打印的点,则需要刷新输出。

最重要的是,这是对互斥锁和条件变量的完全错误使用。条件变量通知表示用户指定的谓词/条件中值的变化:条件的变化和检查它必须是原子的和序列化的:否则会出现数据竞争并且程序的行为将是未定义的。

与示例程序的情况一样:value 由两个线程读写,但没有任何并发​​控制机制,或者换句话说,操作之间没有“先发生”关系,其内容为value和写value的操作。

固定示例如下:

// condition_variable::wait_for example
#include <chrono>             // std::chrono::seconds
#include <condition_variable> // std::condition_variable, std::cv_status
#include <iostream>           // std::cout
#include <mutex>              // std::mutex, std::unique_lock
#include <thread>             // std::thread

std::mutex mtx;
std::condition_variable cv;

int value;

void read_value() {
  int v;
  std::cin >> v;
  std::unique_lock<std::mutex> lck(mtx);
  value = v;
  cv.notify_one();
}

int main() {
  std::cout << "Please, enter an integer (I'll be printing dots): ";
  std::thread th(read_value);

  std::unique_lock<std::mutex> lck(mtx);
  while (cv.wait_for(lck, std::chrono::seconds(1)) == std::cv_status::timeout) {
    std::cout << '.' << std::flush;
  }
  std::cout << "You entered: " << value << '\n';

  th.join();

  return 0;
}

那么,有哪些变化:

  • mutex 被移动到全局范围(为了示例),因此读取value 的线程可以锁定它,以便修改value
  • 读取位于单独的变量中;它不能直接进入value,因为value必须在互斥锁的保护下才能修改,但是持有互斥锁,同时等待输入表单std::cin会阻止主线程打印点,因为它会尝试在超时时获取互斥锁。
  • 每个点输出后,std::cout 被刷新

【讨论】:

  • 互斥锁在对cv.wait_for()的调用中被释放。
  • 不过,正如@selbie 指出的那样,虚假唤醒存在问题(可能,我没有观察到)。
猜你喜欢
  • 1970-01-01
  • 2015-02-27
  • 2017-06-24
  • 2021-09-24
  • 2019-01-22
  • 2020-02-19
  • 2013-10-20
  • 1970-01-01
  • 2010-10-14
相关资源
最近更新 更多