C++ 使用C++;具有原子谓词但没有互斥体的条件变量

C++ 使用C++;具有原子谓词但没有互斥体的条件变量,c++,multithreading,c++11,std,condition-variable,C++,Multithreading,C++11,Std,Condition Variable,我有两条线。一个线程充当计时器线程,它需要定期向另一个线程发送通知。我打算使用C++条件变量。(有一篇关于如何使用C++条件变量及其陷阱和陷阱的好文章) 我有以下限制/条件:- 通知线程不需要锁定到互斥锁 通知(或接收方)线程执行一些有用的部分,但没有关键部分 当且仅当接收方线程正在做有用的工作时,才允许它错过通知 不应该有虚假的唤醒 使用上面的链接作为指导,我整理了以下代码 // conditionVariableAtomic.cpp #include <atomic> #inc

我有两条线。一个线程充当计时器线程,它需要定期向另一个线程发送通知。我打算使用C++条件变量。(有一篇关于如何使用C++条件变量及其陷阱和陷阱的好文章)

我有以下限制/条件:-

  • 通知线程不需要锁定到互斥锁
  • 通知(或接收方)线程执行一些有用的部分,但没有关键部分
  • 当且仅当接收方线程正在做有用的工作时,才允许它错过通知
  • 不应该有虚假的唤醒
  • 使用上面的链接作为指导,我整理了以下代码

    // conditionVariableAtomic.cpp
    
    #include <atomic>
    #include <condition_variable>
    #include <iostream>
    #include <thread>
    #include <iostream>       // std::cout, std::endl
    #include <thread>         // std::this_thread::sleep_for
    #include <chrono>         // std::chrono::seconds
    
    
    std::mutex mutex_;
    std::condition_variable condVar;
    
    std::atomic<bool> dataReady{false};
    
    void waitingForWork(){
        int i = 0;
        while (i++ < 10)
        {
            std::cout << "Waiting " << std::endl;
            {
                std::unique_lock<std::mutex> lck(mutex_);
                condVar.wait(lck, []{ return dataReady.load(); });   // (1)
                dataReady = false;
            }
            std::cout << "Running " << std::endl;
            // Do useful work but no critical section.
        }
    }
    
    void setDataReady(){
        int i = 0;
        while (i++ < 10)
        {
            std::this_thread::sleep_for (std::chrono::seconds(1));
            dataReady = true;
            std::cout << "Data prepared" << std::endl;
            condVar.notify_one();
        }
    }
    
    int main(){
        
      std::cout << std::endl;
    
      std::thread t1(waitingForWork);
      std::thread t2(setDataReady);
    
      t1.join();
      t2.join();
      
      std::cout << std::endl;
      
    }
    
    在作用域块内,也称为
    {..}
    ,以便在等待条件结束时立即解锁互斥锁(接收器然后执行一些有用的工作,但由于没有关键部分,因此不需要在整个while循环中保持互斥锁)。在这种情况下,这种有限的范围界定还会有后果/副作用吗?或者在整个while循环中是否需要锁定
    唯一锁定

    您的代码具有竞争条件。在检查
    wait
    谓词中的
    dataReady
    值和实际启动等待之间,另一个线程可以设置
    dataReady
    并调用
    notify\u one
    。在您的示例中,这并不重要,因为您只会错过一个通知,然后在下一个通知后醒来

    另一个争用条件是,您可以在一个线程中将
    dataReady
    设置为
    true
    ,在另一个线程中将
    dataReady
    设置为
    false
    ,然后在第一个线程中调用
    notify\u one
    ,这同样会导致等待阻塞的时间超过您的预期时间

    设置
    dataReady
    并使用条件变量避免这些争用时,应在两个线程中保持互斥锁


    您可以通过使用原子计数器而不是布尔值来避免第二个争用条件,在一个线程上递增,然后在另一个线程上递减,并在谓词中检查它是否为非零。

    假设我只想避免第二个争用条件,并按照您的建议使用原子计数器。我还需要在通知线程中保持互斥吗?如果不是,那么我们如何避免这种竞争条件-一个线程将此计数器从0增加到1,另一个线程将其减回到0,然后第一个线程调用notify_one。这是否会导致等待阻塞时间延长(如果我们使用的是布尔值)?取决于您如何实现它,如果您将计数器递减的次数与递增的次数相同(即,当一项数据准备就绪时递增,处理该项时递减)那么,当仍然有数据准备就绪时,等待的线程将永远不能减少到
    0
    。谢谢Alan,我明白你的意思。我也在仔细考虑这一点——你提到的第二个比赛条件会在接收者落后并且有通知排队时发生。如果接收者任务在不到一秒钟的时间内完成其工作,则不可能出现这种竞争情况。如果我错了,请纠正我。这两种情况都不太可能发生,但如果你长时间运行你的程序,即使是不太可能的事件最终也会发生enough@PiK使用
    condition\u变量
    时,您必须始终使用互斥锁以避免丢失通知(但请注意,在notify调用过程中通常不需要保持互斥体)。如果要完全避免锁定,则必须等待C++20中引入的atomic_wait/atomic_notify:
    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck, []{ return dataReady.load(); });   // (1)
    dataReady = false;