【问题标题】:Wait for either condition A or condition B in a std::thread在 std::thread 中等待条件 A 或条件 B
【发布时间】:2015-11-04 15:18:46
【问题描述】:

我有一个看似相当简单的需求,但我是使用 std::thread 的新手,我不确定我是否理解正确。

我的线程的工作是运行一个循环:等到一个对象需要处理,然后处理它,然后等待,...

我正要使用condition_variable 来实现这一点,但我意识到当线程空闲等待新对象时,它不会注意到stopThread 标志已设置。

我实际上想要一种方法来做wait_for_either(new_data,exit_thread),但我不确定如何优雅地实现它。类似队列函数的旧代码使用 Windows API WaitForMultipleObjects 但我想借此机会学习 C++11 方式。

【问题讨论】:

    标签: c++ multithreading c++11 stl


    【解决方案1】:

    当您等待数据处理时,您实际上是在等待条件变量发出信号。所以当你想退出线程的时候只要给条件变量发信号,就好像stopThread标志是一个特殊的要处理的数据一样。

    代码可能如下所示:

    void thread_func()
    {
        std::unique_lock<std::mutex> lock(mutex);
        for (;;)
        {
            cond.wait(lock, [] () { return stopThread || !dataContainer.empty(); });
            if (stopThread)
                return; //exit thread
            //process data from dataContainer
        }
    }
    

    插入数据:

    {
        std::unique_lock<std::mutex> lock(mutex);
        dataContainer.push_back(new_data);
        cond.notify_all();
    }
    

    然后,当你想停止线程时:

    {
        std::unique_lock<std::mutex> lock(mutex);
        stopThread = true;
        cond.notify_all();
    }
    thread.join(); //not necessary but probably a good idea
    

    【讨论】:

    • 我认为您的意思是使用 wait 而不是 wait_forwait_for 需要指定持续时间。
    • 这将使用condition_var_any,对吗?我假设条件函数只是定期调用 - 这个函数需要是线程安全的还是有保证的?
    • @Mr.Boy。 std::condition_variablestd::condition_variable_any,没关系。每当其他线程调用notify_one()notify_all() 时都会调用条件函数,并且会在关联锁被锁定的情况下调用它,因此您可以从那里访问共享数据。
    • 致 OP 和其他任何处理条件变量的人:请阅读.wait 调用的 lambda 部分如何工作。它可以并且将被虚假评估(持有锁)。 (至少据我所知,这个答案正确地使用了它)
    • @Mr.Boy:不,这就是条件变量的美妙之处。 wait() 调用首先释放互斥体,然后等待通知。当通知最终到达时,互斥锁被重新锁定并检查条件。如果条件返回 false,则再次释放锁并再次等待。如果条件返回 true,wait() 将返回并锁定互斥锁。而这一切都是免费的!
    【解决方案2】:

    这是一个带有中止选项的强大数据消耗循环:

    while(true) {
      decltype(dataContainer) data;
      {
        std::unique_lock<std::mutex> lock(mutex);
        cond.wait(lock, [] () { return stopThread || !dataContainer.empty(); });
        if (stopThread)
          return; //exit thread
        data = std::move(dataContainer);
      }
      for (auto&& d:data) {
        if (stopThread) return; // abort
        //process data from d
      }
    }
    

    stopThread 应该是atomic 或者底部的for(:) 循环中的访问需要用mutex 保护。

    for(:) 循环中对stopThread 的访问是可选的;没有它,它在完成它拾取的工作包之前不会中止。

    dataContainer 是以std::vector 或类似的形式完成的一系列工作。线程唤醒,抓取所有待办事项,然后继续工作。

    您也可以从dataContainer 弹出一项任务,而不是全部执行。生成的代码稍微简单一些。

    要将数据排队到dataContainer,你必须锁定mutex,放入数据,然后通知:

    {
      std::unique_lock<std::mutex> lock(mutex);
      dataContainer.push_back(new_data);
    }
    cond.notify_one();
    

    关闭:

    {
      std::unique_lock<std::mutex> lock(mutex);
      stopThread = true;
    }
    cond.notify_all();
    

    请注意,即使stopThread 是原子的,您也需要获取互斥锁。否则存在竞争条件。

    【讨论】:

    • 我喜欢您将数据拉出然后释放锁的方式,因此在处理当前条目时可以将更多条目排入队列。拉取数据后是否可以手动释放锁达到同样的效果?
    • @Mr.Boy 当然可以。明确弄乱锁定状态时要小心;范围锁更容易推理。
    【解决方案3】:

    半 - 伪代码。

    std::atomic_bool programRunning;
    std::condition_variable cv;
    std::mutex mtx;
    
    std::thread loopThread([&]{
       while(programRunning.load()){
           std::unique_lock<std::mutex> lock(mtx);
            if (newDataAvailable){
                //process new data
            } else{
                cv.wait(lock,[&]{ return dataAvailable || !progamRunning.load(); });
            }
       }
    
    });
    
    {
       std::lock_guard<std::mutex> lock(mtx);
       queueMoreData();
       cv.notify_one();
    }
    
    //on exit:
    programRunning.store(false);
    cv.notify_one();
    

    【讨论】:

    • 我知道你在使用 lambdas 但[&amp;] {...} 语法是什么?
    • 通过引用捕获所有内容,没有参数
    • 我会做一个永远循环,等待,然后打破自己。此外,您应该使您的 newDataAvailable 具有相同的名称。 ;) 接下来,您可能不想在处理数据的整个期间都持有互斥锁;这使得排队新数据的人不得不等待处理旧数据。
    • @Yakk 我也是这么想的。此外,如果我使用常规布尔值和锁来控制对programRunning 的访问,这意味着我的互斥锁将“停滞” - 我无法添加新数据,因为线程在等待数据时总是将其锁定。使用两个互斥锁似乎有点 OTT。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-19
    • 2015-11-15
    • 1970-01-01
    • 2017-06-04
    相关资源
    最近更新 更多