我正在尝试实现以下功能:
Usecase:我正在为数控机床编写软件。电机的步进脉冲是在实时线程中在软件中生成的。这个实时线程不断更新一个保持轴当前位置的变量。多个其他非实时线程可以读取该变量,例如显示当前位置。
问题1:对于这类问题是否有标准/公认的解决方案或模式?
我想出了以下想法:使用std::atomic<uint64_t>保护线程当前正在写入的数据和跟踪天气(通过检查最后一个位)或从读取开始就已经写入(通过增加写入的值)。
template <class DATA, class FN>
void read_modify_write(DATA& data, std::atomic<uint64_t>& protector, FN fn)
{
auto old_protector_value = protector.load();
do
{
// wait until no other thread is writing
while(old_protector_value % 2 != 0)
old_protector_value = protector.load();
// try to acquire write privileges
} while(!protector.compare_exchange_weak(old_protector_value, old_protector_value + 1));
// write data
data = fn(data);
// unlock
protector = old_protector_value + 2;
};
template <class DATA>
DATA read(const DATA& data, std::atomic<uint64_t>& protector)
{
while(true)
{
uint64_t old_protector_value = protector.load();
// wait until no thread is writing
while(old_protector_value % 2 != 0)
old_protector_value = protector.load();
// read data
auto ret = data;
// check if data has changed in the meantime
if(old_protector_value == protector)
return ret;
}
}问题2:上述代码线程安全并满足上述要求吗?
问题3:可以改进吗?
(我能找到的唯一理论问题是,如果计数器环绕,即在1读操作期间执行2^63写入操作。如果没有更好的解决方案,我认为这个弱点是可以接受的。)
谢谢
发布于 2020-10-01 12:51:46
严格地说,您的代码不是无锁的,因为您有效地使用protector的LSB实现自旋锁。
您的解决方案看起来非常像一个序列锁。然而,实际的读取操作auto ret = data;严格来说是一种数据竞争。公平地说,在C++17中编写完全符合标准的seqlock是不可能的,因为我们必须等待C++20。
可以扩展seqlock,使读取操作无锁,而代价是更高的内存使用量。其思想是拥有多个数据实例(让我们将它们称为时隙),并且写操作总是以循环方式写入下一个插槽。这允许读取操作从最后一个完全写入的槽中读取。德米特里·维尤科夫( Dmitry )在改进的无锁SeqLock中描述了他的做法。您可以看看我的seqlock实现,它是我xenium库的一部分。它还允许使用可配置的槽数进行无锁读取操作(尽管它与Vyukov在找到最后一个完整写入的插槽方面略有不同)。
https://stackoverflow.com/questions/64147529
复制相似问题