【发布时间】: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
【问题讨论】:
-
@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