gpt4 book ai didi

c++ - 为什么不重新锁定互斥锁的 condition_variable 没有等待函数

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:10:57 26 4
gpt4 key购买 nike

考虑以下示例。

std::mutex mtx;
std::condition_variable cv;

void f()
{
{
std::unique_lock<std::mutex> lock( mtx );
cv.wait( lock ); // 1
}
std::cout << "f()\n";
}

void g()
{
std::this_thread::sleep_for( 1s );
cv.notify_one();
}

int main()
{
std::thread t1{ f };
std::thread t2{ g };
t2.join();
t1.join();
}
g() “知道” f()正在等待我想讨论的场景。
根据 cppreference.com不需要 g()在调用之前锁定互斥锁 notify_one .现在在标记为“1”的行中 cv将释放互斥锁并在发送通知后重新锁定它。 lock 的析构函数之后立即再次释放它。这似乎是多余的,特别是因为锁定是昂贵的。 (我知道在某些情况下需要锁定互斥锁。但这里不是这种情况。)

为什么 condition_variable没有函数“ wait_nolock ”一旦通知到达就不会重新锁定互斥锁。如果答案是 pthreads 不提供这样的功能:为什么 pthreads 不能扩展以提供它?是否有实现所需行为的替代方法?

最佳答案

你误解了你的代码是做什么的。

您的在线代码// 1完全不阻塞是自由的。 condition_variables可以(并且将会!)有虚假的唤醒——他们可以毫无理由地唤醒。

您负责检查唤醒是否是虚假的。

使用 condition_variable正确地需要三件事:

  • 一个 condition_variable
  • 一个 mutex
  • mutex 保护的一些数据

  • 由互斥锁保护的数据被修改(在 mutex 下)。然后( mutex 可能脱离), condition_variable被通知。

    在另一端,您锁定了 mutex ,然后等待条件变量。当您醒来时,您的 mutex被重新锁定,您可以通过查看 mutex 保护的数据来测试唤醒是否是虚假的。 .如果是有效唤醒,则处理并继续。

    如果它不是有效的唤醒,您将返回等待。

    在您的情况下,您没有保护任何数据,无法区分虚假唤醒和真实唤醒,并且您的设计不完整。

    毫不奇怪,由于设计不完整,您看不到 mutex 的原因。被重新锁定:它被重新锁定,因此您可以安全地检查数据以查看唤醒是否是虚假的。

    如果您想知道为什么这样设计条件变量,可能是因为这种设计比“可靠”的设计更有效(无论出于何种原因),并且 C++ 并没有公开更高级别的原语,而是公开了更低级别的更高效的原语。

    在此基础上构建更高级别的抽象并不难,但需要进行设计决策。这是一个建立在 std::experimental::optional 之上的:
    template<class T>
    struct data_passer {
    std::experimental::optional<T> data;
    bool abort_flag = false;
    std::mutex guard;
    std::condition_variable signal;

    void send( T t ) {
    {
    std::unique_lock<std::mutex> _(guard);
    data = std::move(t);
    }
    signal.notify_one();
    }
    void abort() {
    {
    std::unique_lock<std::mutex> _(guard);
    abort_flag = true;
    }
    signal.notify_all();
    }
    std::experimental::optional<T> get() {
    std::unique_lock<std::mutex> _(guard);
    signal.wait( _, [this]()->bool{
    return data || abort_flag;
    });
    if (abort_flag) return {};
    T retval = std::move(*data);
    data = {};
    return retval;
    }
    };

    现在,每个 send可能导致 get在另一端取得成功。如果不止一个 send发生时,只有最新的一个被 get 消耗。 .如果以及何时 abort_flag设置,而不是 get()立即返回 {} ;

    以上支持多个消费者和生产者。

    如何使用上述内容的一个示例是预览状态源(例如 UI 线程)和一个或多个预览渲染器(它们的速度不够快,无法在 UI 线程中运行)。

    预览状态将预览状态转储到 data_passer<preview_state>随意。渲染器竞争,其中之一抢占了它。然后他们渲染它,并将它传回(通过任何机制)。

    如果预览状态的速度比渲染器消耗它们的速度快,则只有最近的状态是感兴趣的,因此较早的状态将被丢弃。但是现有的预览不会因为出现新状态而中止。

    下面询问有关竞争条件的问题。

    如果正在通信的数据是 atomic ,我们不能没有“发送”端的互斥锁吗?

    所以像这样:
    template<class T>
    struct data_passer {
    std::atomic<std::experimental::optional<T>> data;
    std::atomic<bool> abort_flag = false;
    std::mutex guard;
    std::condition_variable signal;

    void send( T t ) {
    data = std::move(t); // 1a
    signal.notify_one(); // 1b
    }
    void abort() {
    abort_flag = true; // 1a
    signal.notify_all(); // 1b
    }
    std::experimental::optional<T> get() {
    std::unique_lock<std::mutex> _(guard); // 2a
    signal.wait( _, [this]()->bool{ // 2b
    return data.load() || abort_flag.load(); // 2c
    });
    if (abort_flag.load()) return {};
    T retval = std::move(*data.load());
    // data = std::experimental::nullopt; // doesn't make sense
    return retval;
    }
    };

    以上无法正常工作。

    我们从监听线程开始。它执行步骤 2a,然后等待 (2b)。它在步骤 2c 评估条件,但尚未从 lambda 返回。

    广播线程然后执行步骤 1a(设置数据),然后通知条件变量。此时,没有人在等待条件变量(lambda 中的代码不算数!)。

    监听线程然后完成 lambda,并返回“虚假唤醒”。然后它阻塞在条件变量上,并且永远不会注意到数据已发送。
    std::mutex在等待条件变量时使用必须保护对条件变量“传递”的数据的写入(无论您做什么测试以确定唤醒是否是虚假的),以及读取(在 lambda 中),或“丢失”的可能性信号”存在。 (至少在一个简单的实现中:更复杂的实现可以为“常见情况”创建无锁路径,并且只在双重检查中使用 mutex。这超出了这个问题的范围。)

    使用 atomic variables 并没有解决这个问题,因为“确定消息是否是虚假的”和“在条件变量中重新等待”这两个操作对于消息的“虚假性”必须是原子的。

    关于c++ - 为什么不重新锁定互斥锁的 condition_variable 没有等待函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32978066/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com