代码之家  ›  专栏  ›  技术社区  ›  John

为什么条件变量::notify\u all不能唤醒任何线程?

  •  0
  • John  · 技术社区  · 4 年前

    我用 conditional_variable::notify_all() 唤醒等待的线程( 只有一根线 正在等待 unique_lock ).

    此代码段在大多数情况下工作正常,但日志文件(有关详细信息,请参阅下文)表明父线程无法获取

    如果能在这个问题上得到一些帮助,我将不胜感激。

    以下是相关代码段:

    void MainWindow::deployAction(void)
    {
        std::condition_variable cvRunOver;
        std::mutex mtxRunOver;
        std::unique_lock <std::mutex> ulkRunOver(mtxRunOver);
        QString workerThreadRes;
        std::thread workThread([&]()
        {
            workThread.detach();
    
            do_some_process_for_seconds();
            
            cvRunOver.notify_all();
            LOG(INFO)<<"to leave the subthread";
            google::FlushLogFiles(google::GLOG_INFO);
            return;
        });
    
        while (cvRunOver.wait_for(ulkRunOver, std::chrono::milliseconds(100)) == std::cv_status::timeout)
        {
            qApp->processEvents();
            auto curTim = std::chrono::steady_clock::now();
            std::chrono::duration<float> escapedTim= curTim-lastTim;
            if(std::chrono::duration_cast<std::chrono::seconds>(escapedTim).count()>=5)
            {
                LOG(INFO) << "processEvents()";
                google::FlushLogFiles(google::GLOG_INFO);
                lastTim = curTim;
            }
        }
        
        LOG(INFO) << "get lock and continue to run";
        google::FlushLogFiles(google::GLOG_INFO);
    }
    

    以下是程序无法正常运行时的相关日志:

    Log line format: [IWEF]hh:mm:ss.uuuuuu threadid file:line] msg
    20:19:14.638686 272568 mainwindow.cpp:208] to leave the subthread
    20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
    20:19:22.678846 10256 mainwindow.cpp:221] processEvents()
    20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
    20:19:22.678846 10256 mainwindow.cpp:221] processEvents()
    20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
    20:19:22.678846 10256 mainwindow.cpp:221] processEvents()
    20:19:17.669246 10256 mainwindow.cpp:221] processEvents()
    ...
    
    0 回复  |  直到 4 年前
        1
  •  5
  •   David Schwartz    4 年前

    您误用了条件变量。要使用条件变量:

    1. 一根线 必须 正在通知另一个线程共享状态的某些更改。

    2. 一定有某种共享状态已经改变了。

    3. 共享状态必须由与条件变量关联的互斥体保护。

    4. 在决定等待之前,必须测试共享状态。

    5. 在发送信号或广播之前,执行信号或广播的线程必须在互斥保护下更改了共享状态。

    失败。您似乎没有任何共享状态受到mutex的保护,您正在使用condition变量将其更改通知另一个线程。没有这一点,你就无法做出是否等待的正确决定,最终你将等待已经发生的事情。

    看到了吗 this answer 更多信息。

    你的代码有这个缺陷,因为你的代码决定等待而没有首先检查它等待的事情是否已经发生,违反了规则4。您似乎也违反了规则3,因为我没有看到任何受互斥保护的共享状态。你可能违反了规则5,因为我看不到你的答案 workThread 在调用notify函数之前更改任何共享状态。

    here 要显示所有规则的工作方式:

        // condition_variable example
        #include <iostream>           // std::cout
        #include <thread>             // std::thread
        #include <mutex>              // std::mutex, std::unique_lock
        #include <condition_variable> // std::condition_variable
    
        std::mutex mtx;
        std::condition_variable cv;
        bool ready = false;
    
        void print_id (int id) {
          std::unique_lock<std::mutex> lck(mtx);
          while (!ready) cv.wait(lck); // rules 3 and 4 ("ready" is the shared state)
          // ...
          std::cout << "thread " << id << '\n';
        }
    
        void go() {
          std::unique_lock<std::mutex> lck(mtx); // rule 3
          ready = true; // rules 1 and 2
          cv.notify_all(); // rule 5
        }
    
        int main ()
        {
          std::thread threads[10];
          // spawn 10 threads:
          for (int i=0; i<10; ++i)
            threads[i] = std::thread(print_id,i);
    
          std::cout << "10 threads ready to race...\n";
          go();                       // go!
    
          for (auto& th : threads) th.join();
    
          return 0;
        }
    
        2
  •  1
  •   zkoza    4 年前

    大卫的回答很好。我只想澄清几点。看这张照片:

    Consumer-Producer

    条件变量的主要思想是启用被动同步。我所说的被动是指这样的,即不会在绝望中耗尽CPU while while (!producer.has.data()) continue;

    请注意,只有在锁定的互斥锁下才能接触共享数据。

    请记住,醒来就像按一次铃。如果要唤醒的线程未处于睡眠状态,它将错过警报。通常这就是你想要的:如果消费者没有睡着,它就不需要你的数据了。如果它需要这些数据,它将在不睡觉的情况下使用这些数据。所以你可以想象制片人是查尔斯卓别林,站在传送带旁边,每次他“生产”一些东西,他都会按门铃。但他不知道,也不在乎是否有人能听到。也许这就是为什么这个函数被称为“notify”,而不是signal,因为通常必须接收信号。通知不起作用。

    bool shared_ready !shared_container.empty() ".

    在图中,生产者在被锁定时通知另一个线程。不需要这样,顺序可以颠倒(先解锁,然后通知其他线程)。

    如果你已经走到了这一步,那么你已经准备好接受参考资料中的专业描述了 Condition Variable

    请看下面的例子。如何使用lambda检查状况。这是使用条件变量的首选方法:使用它,您不能忘记条件!