避免条件变量中的错误唤醒通知未知数量的线程

Avoid false wake-ups in conditional variable notifying unknown number of threads

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

等待变量的线程醒来后,将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<std::thread::id,bool> 跟踪所有更新之外,还有其他方法吗?

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

注意:我还将 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; });
    }

};

如果控制器线程在启动更新之前等待所有 worker 没问题,则循环计数器可以与来回翻转的 bool 交换,从而避免溢出问题。

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() 的工作线程将只会在下一个周期中得到通知。