【问题标题】:Avoid false wake-ups in conditional variable notifying unknown number of threads避免在通知未知线程数的条件变量中出现错误唤醒
【发布时间】:2020-07-13 10:12:59
【问题描述】:

我正在使用条件变量定期通知未知数量的线程。 即循环将定期修改一个值,然后通知所有等待该变量的线程。 避免错误唤醒的常用方法是使用布尔变量。

线程唤醒后等待变量,将bool的值设置为false

class foo
{
    std::mutex mtx;
    std::condition_variable cv;
    bool update;
    public:

    void loop()
    {
        std::this_thread::seep_for(std::chrono::milliseconds(100));
        {
            std::lock_guard<std::mutex> lock(mtx);
            update = true;
        }
        cv.notify_all();
    }

    void wait()
    {
        while(!update)
        {
            std::unique_lock<std::mutex> lock(mtx);
            cv.wait();
            if(update)
            {
                update = false;
                break;
            }
        }
    }

};

函数foo::loop在一个线程中启动,其他执行周期性过程的线程将通过函数foo::wait等待。

但由于不同的线程可能在不同的时间开始等待,似乎每个线程都需要一个专用的布尔变量来确保不会出现错误的唤醒。

由于我不知道有多少线程可能在等待它,所以我不能使用 bool 数组或任何东西。

除了使用std::map&lt;std::thread::id,bool&gt; 来跟踪所有更新之外,还有其他方法吗?

【问题讨论】:

  • 更新和更新是两个不同的东西?
  • @Surt 哦,对不起,不是,它们都是一样的,这是一个错字,已更正。
  • 你为什么不用notify_one?一个服务员线程将update设置为false,所以其他所有的服务员线程在唤醒后都会立即挂起。
  • @geza 我希望所有线程都获取更新,所以如果将更新设置为 false,所有线程将进一步阻塞。
  • 这就是我问的原因。如果您之后发送除一个之外的所有线程进入睡眠状态,为什么还要唤醒所有线程?如果您的工作线程运行相同的代码,这是一种资源浪费。如果他们运行不同的代码,那么您应该为每种工作线程创建单独的 cv。

标签: c++ multithreading wakeup


【解决方案1】:

通过使用循环计数器,您可以确保所有线程都在通知时唤醒,并且没有错误的唤醒。唯一的问题是循环计数器溢出的可能性。

注意:我还将 cv.wait() 谓词移到了 lambda 中。

class foo
{
    std::mutex mtx;
    std::condition_variable cv;
    int cycle_count = 0;
    public:

    void loop()
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        {
            std::lock_guard<std::mutex> lock(mtx);
            ++cycle_count;
        }
        cv.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(mtx);
        int expected_cycle = cycle_count + 1;
        cv.wait(lock, [expected_cycle](){ return cycle_count >= expected_cycle; });
    }

};

如果控制器线程在启动更新之前等待所有工作人员是可以的,则可以将循环计数器与来回翻转的布尔值交换,从而避免溢出问题。

class foo
{
    std::mutex mtx;
    std::condition_variable cv;
    bool is_cycle_count_even = true;
    int threads_waiting;
    int threads_to_notify;
    public:

    void loop()
    {
        while (true) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            std::lock_guard<std::mutex> lock(mtx);
            if (threads_to_notify == 0) { break; }
        }

        {
            std::lock_guard<std::mutex> lock(mtx);
            threads_to_notify = threads_waiting;
            threads_waiting = 0;
            is_cycle_count_even ^= true;
        }
        cv.notify_all();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(mtx);
        ++threads_waiting;
        bool expected_cycle = !is_cycle_count_even;
        cv.wait(lock, [expected_cycle](){ return is_cycle_count_even == expected_cycle; });
        --threads_to_notify;
    }

};

这样,在控制器已经开始通知(或至少在通知之前获得互斥锁)之后进入wait()的工作线程只会在下一个周期得到通知。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-31
    • 2017-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-23
    • 1970-01-01
    相关资源
    最近更新 更多