【发布时间】:2017-01-01 21:39:39
【问题描述】:
我正在将一个在准系统上运行的项目迁移到 linux,并且需要消除一些 {disable,enable}_scheduler 调用。 :)
所以我需要在单个写入器、多个读取器的场景中使用无锁同步解决方案,其中写入器线程不能被阻塞。我想出了以下解决方案,它不适合通常的获取-释放顺序:
class RWSync {
std::atomic<int> version; // incremented after every modification
std::atomic_bool invalid; // true during write
public:
RWSync() : version(0), invalid(0) {}
template<typename F> void sync(F lambda) {
int currentVersion;
do {
do { // wait until the object is valid
currentVersion = version.load(std::memory_order_acquire);
} while (invalid.load(std::memory_order_acquire));
lambda();
std::atomic_thread_fence(std::memory_order_seq_cst);
// check if something changed
} while (version.load(std::memory_order_acquire) != currentVersion
|| invalid.load(std::memory_order_acquire));
}
void beginWrite() {
invalid.store(true, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
}
void endWrite() {
std::atomic_thread_fence(std::memory_order_seq_cst);
version.fetch_add(1, std::memory_order_release);
invalid.store(false, std::memory_order_release);
}
}
我希望意图很明确:我在beginWrite/endWrite 之间包装了一个(非原子)有效负载的修改,并仅在传递给sync() 的 lambda 函数内读取有效负载。
如您所见,我在beginWrite() 中有一个原子存储,在存储操作之后没有写入可以在存储之前重新排序。我没有找到合适的例子,而且我完全没有这方面的经验,所以我想确认一下它是可以的(通过测试验证也不容易)。
此代码是否无竞争且按预期工作?
如果我在每个原子操作中都使用 std::memory_order_seq_cst,我可以省略栅栏吗? (即使是,我估计性能会更差)
我可以在 endWrite() 中放下围栏吗?
我可以在栅栏中使用 memory_order_acq_rel 吗?我真的不明白其中的区别——我不清楚单个总订单的概念。
是否有任何简化/优化的机会?
+1。我很乐意接受任何更好的想法作为这个类的名称:)
【问题讨论】:
-
作为一个建议:对我来说,使用 Relacy Race Detector 大大简化了处理此类问题的过程。 SPIN/Promela 等经典工具不直接支持高级内存模型。 github.com/dvyukov/relacy
-
您的代码已损坏:如果编写器碰巧在阅读器执行
version.load(std::memory_order_acquire) != currentVersion)和invalid.load(std::memory_order_acquire)条件之间执行endWrite(),则阅读器不会看到版本凸起或引发的无效标志,即使它的代码与编写器代码并发运行。在这种情况下,作者可能早在读者完成对无效标志的等待循环时就调用了beginWrite(),显然破坏了读取数据的一致性。 -
你是对的。如果我在 do..while() 条件下更改测试顺序,先检查无效,再检查版本怎么办?
标签: c++11 atomic lock-free memory-fences memory-barriers