【发布时间】:2018-03-23 10:12:28
【问题描述】:
我不确定我能否在标题中正确地压缩这个概念,但我的意思是:
假设我有“慢”,从非常不频繁的意义上说,单个线程对数据结构进行更新,而有多个线程连续读取相同的数据结构。
为了避免锁定,并被卡在 C++14(因此没有可用的 std::shared_mutex)并且没有提升,我想到了一种方法,我保留结构的 2 个副本并使用原子整数来索引当前的。
假设 2 的深度就足够了,我们不用担心,因为更新非常罕见,以至于在新的更新到来之前,有足够的时间让所有读者“看到”数据结构的新版本.
这是一个 sn-p,它显示了我正在做的事情的简化版本:
/*
* includes...
*/
struct datastructure_t {
/*...*/
};
class StructSwapper
{
std::atomic<unsigned int> current_index_;
datastructure_t structures_[2];
public:
StructSwapper (datastructure_t s)
: current_index_(0)
, structures_{std::move(s), {}}
{}
//Guaranteed to be called _infrequently_ by the same single thread
void update (datastructure_t newdata)
{
auto const next_index = !current_index_.load();
structures_[next_index] = std::move(newdata);
current_index_.store(next_index);
}
//Called _frequently_ by multiple threads
datastructure_t const & current_data() const
{
return structures_[current_index_.load()];
}
};
所以基本上当编写器线程执行更新时,它首先修改数据结构的“影子”副本,然后原子地更新指向它的索引。
每个阅读器线程都会执行以下操作:
void reader_thread(StructSwapper const &sw)
{
auto const ¤t_data = sw.current_data();
if (current_data->find(...)) //1
{
do_something (current_data->val1); //2
if (current_data->property2) //3
do_something_else (current_data->val2); //4
/*...*/
}
}
但后来我开始思考:是什么保证编译器不会在任何标记为 1、2、3、4 的行中重新读取 current_data 的值,然后在执行过程中可能会得到它的两个不同版本这个函数是否同时被写入线程执行了更新?
也许如果 StructSwapper::current_data() 是内联的,它可能会查看它并将原子变量用作索引,但我怀疑这是否足够。
那么,两个问题:
- 我是否认为这种方法不能保证有效,因为编译器不知道 current_data 的“快照”实际上只能拍摄一次?
- 我认为,如果我改为返回对数据结构当前版本的原子引用,这可能会有所不同,因为在这种情况下,编译器会理解它可以从两个不同的读取中获得两个不同的值,对吧?
编辑:在看到与更宽松的内存排序相关的优化建议后,我想补充一下,我没有在上面的 sn-p 中报告它们,但它们确实已经在实际代码中到位。
【问题讨论】:
标签: c++ multithreading c++11 atomic