0

conditional_variable::notify_all()用来唤醒一个等待线程(只有一个线程在等待unique_lock 确实)。

这段代码大部分时间都运行良好,但是日志文件(详见下文)表明,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()
...
4

2 回答 2

5

您正在滥用条件变量。要使用条件变量:

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

  2. 实际上必须有一些共享状态发生了变化。

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

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

  5. 做信号或广播的线程必须在信号或广播之前在互斥锁的保护下改变共享状态。

如果你不遵循这四个规则,你的代码总是会失败。您似乎没有任何受互斥锁保护的共享状态,您使用条件变量通知另一个线程的更改。没有这个,你就无法做出是否等待的正确决定,你最终会等待已经发生的事情。

有关更多信息,请参阅此答案

想象一下,如果你和你姐姐共用一辆汽车。你让你姐姐在她把车开回来时按铃,这样你就不用等了。现在假设您想使用汽车,因此您等待铃声响起。如果您决定等待时您的姐姐没有使用汽车,您将等待很长时间!

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

我在示例代码中添加了一些注释,以展示所有规则的工作原理:

    // 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;
    }
于 2021-03-12T02:59:19.777 回答
2

大卫的回答非常好。我只想澄清一些观点。看这张图片:

消费者-生产者

一个线程是粉红色的,另一个是蓝色的,同步机制是绿色的。

条件变量的主要思想是启用被动同步。被动是指不会在绝望的while循环中烧毁 CPU ( while (!producer.has.data()) continue;)。因此,您需要一些共享数据,这些数据将随着程序的发展而改变。您需要一个互斥锁来再次保护数据的竞争条件。那么条件变量是安眠药和闹钟的组合。

请注意,共享数据只能在锁定的互斥锁下被访问。

请记住,醒来就像敲响一次钟声。如果要唤醒的线程没有睡着,就会错过闹钟。通常这就是你想要的:如果消费者没有睡着,它确实不需要你的数据(还)。如果它需要数据,它会在不入睡的情况下使用它。所以你可以把制片人想象成查尔斯卓别林站在传送带旁边,每次他“生产”一些东西时,他都会按铃。但他不知道,也不关心,是否有人能听到。也许这就是为什么该函数被称为“通知”而不是信号的原因,因为通常必须接收信号。通知没有。

图中有一个神秘的“OS”(操作系统)元素。是的,由条件变量“控制”的线程可能会被操作系统直接唤醒。这就是一些操作系统的工作方式。也许他们想确保没有线程死亡。所以当你醒来时,你必须,必须,必须检查你是被生产者唤醒的,还是被操作系统唤醒的。为此,您需要检查与共享数据状态相关的 CONDITION。所以需要获取锁(这个是自动原子完成的,图中没说清楚)并读取一些共享数据。它可以只是一个普通bool shared_ready的说“数据准备好/没有准备好”或数据的条件,如“ !shared_container.empty()”。

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

如果您已经走到了这一步,那么您已经为 cppreference条件变量中的专业描述做好了准备

请看那里的例子。如何使用 lambda 来检查条件。这是使用条件变量的首选方式:使用它,您不能忘记 CONDITION!

于 2021-03-12T12:35:02.707 回答