【问题标题】:shared_mutex lock orderingshared_mutex 锁排序
【发布时间】:2019-05-11 15:28:20
【问题描述】:

我的印象是,如果获取了太多共享锁,使用 c++17 的std::shared_mutex 实现的多读取器/单写入器模式可能永远不会放弃唯一锁。

在挖掘cppreference 之后,我不确定情况是否如此。具体来说:

单个互斥体上的所有锁定和解锁操作都发生在单个互斥体上 总订单

例如,给定shared_mutex 上的以下操作,我相信unique_lock 可能永远不会获取。假设无限数量的shared_locks,并且这些锁在第一个shared_locks 释放之前获得。

shared_lock
shared_lock
shared_lock

unique_lock

shared_lock
[...]
shared_lock

给出以下特征。

{ shared_lock, shared_lock, shared_lock, shared_lock, ..., shared_lock } // never releases

unique_lock

但是,如果我正确理解 cppreference,一旦 unique_lock 尝试获取,连续的 shared_locks 将阻塞,直到 unique_lock 被释放。给出以下线程特性。

{ shared_lock, shared_lock, shared_lock} // simultaneous

unique_lock

{ shared_lock, ..., shared_lock} // waits, then simultaneous

所以我的问题是,std::shared_mutex 是否在共享锁和唯一锁之间保持排序?防止由于大量shared_locks被获取而从未获取unique_locks的情况。

编辑:

这是一个代码示例,可帮助您理解问题,并为后代着想。在 MSVC 2019 上,shared_mutex 是安全的,并且可以根据需要进行订购。 unique_lock 确实在 shared_locks 的“无限”数量之前得到处理。

现在的问题是,这个平台是否依赖?

#include <chrono>
#include <cstdio>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <vector>

using namespace std::chrono_literals;

std::shared_mutex smtx;

int main(int, char**) {

    std::vector<std::thread> threads;

    auto read_task = [&]() {
        std::shared_lock l{ smtx };
        printf("read\n");
        std::this_thread::sleep_for(1s);
    };

    auto write_task = [&]() {
        std::unique_lock l{ smtx };
        printf("write\n");
        std::this_thread::sleep_for(1s);
    };

    // Create a few reader tasks.
    threads.emplace_back(read_task);
    threads.emplace_back(read_task);
    threads.emplace_back(read_task);


    // Try to lock a unique_lock before read tasks are done.
    std::this_thread::sleep_for(1ms);
    threads.emplace_back(write_task);

    // Then, enque a gazillion read tasks.
    // Will the unique_lock be locked? [drum roll]

    // Would be while(true), 120 should be enough for demo
    for (size_t i = 0; i < 120; ++i) {
        std::this_thread::sleep_for(1ms);
        threads.emplace_back(read_task);
    }

    for (auto& t : threads) {
        t.join();
    }
}

输出:

read
read
read
write
read
...
read

【问题讨论】:

  • 该标准缺少与 WaitForMultipleObjects (Windows) 等效的功能,这使得单个写入器/多个读取器的场景不可靠。检查我的RWMutextlock for Windows
  • @MichaelChourdakis 所以不,std::shared_mutex 不是“那么聪明”。并且必须使用其他机制(如 2 个 ring_buffers)来收集和调度正确数量的工作。处理太多读任务永远不允许写任务执行的情况。
  • @MichaelChourdakis 您能否详细说明为什么std::shared_mutex 对于共享对象场景中的单个写入器/多个读取器不可靠?似乎这种情况首先是它存在的全部原因。如果它不能可靠地处理这个问题,那么这似乎是标准中的一个严重缺陷。似乎没有任何公开的 LWG 或 CWG 问题涉及 std::shared_mutex 的可靠性,所以如果您知道那里有这样的问题,可能应该立即报告……
  • @HowardHinnant:defer readers 的读者-作者锁定实现允许作者(最终)继续。问题是 C++ 是否保证行为(一种方式或另一种方式)。
  • 在检查 shared_mutex 之后,在我看来这是安全的,它在 for 循环中等待所有读者,但是当线程退出时,它不再能够重新进入。因此它是安全的,虽然速度较慢。我的实现允许读取线程重新进入,只要至少一个读取器还没有完成,因此使用了 WaitForMultipleObjects,并且还支持升级。

标签: c++ multithreading locking c++17 mutex


【解决方案1】:

std shared_mutex 规范没有指定共享锁和唯一锁的优先级。也没有任何 API 可以设置这样的优先级。缺乏优先级规范的最初动机之一是Alexander Terekhov algorithm as explained here 的存在。

次要动机是解释缺乏读写器 shared_mutex 中的优先级策略。这是由于算法 归功于 Alexander Terekhov,它让操作系统决定哪个线程 是下一个获得锁的人,而不关心是否独特的锁或 正在寻找共享锁。这导致完全缺乏阅读器 或作家饥饿。这很公平。

标准规范不需要 Alexander Terekhov 算法。但是,至少我希望这种算法会更受欢迎,因为缺少规范或 API 来让读者优先于编写者,反之亦然。

this SO answer here 中有更多关于 Alexander Terekhov 算法的详细信息和一些演示其行为的代码。

【讨论】:

  • “这导致完全没有读者或作者饥饿。这很公平”。所以是的,unique_lock 将按预期进行处理,并且编写器线程不会被饿死?您还想要代码示例吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多