C++ 任意大小数据/向量/数组的原子和无锁写入

C++ 任意大小数据/向量/数组的原子和无锁写入,c++,multithreading,atomic,C++,Multithreading,Atomic,我正在尝试实现以下功能: 任意大小的数据类型的原子和无锁写或读修改写(在我的例子中,通常是一个最多有6个元素的浮点/整数向量) 从上述数据类型读取的原子不会阻止写入线程。读操作可能被写操作阻止 用例:我正在尝试为CNC机器编写软件。电机的阶跃脉冲在软件中以实时线程的形式生成。此实时线程不断更新保持轴当前位置的变量。多个其他非实时线程可以读取该变量,例如显示当前位置 问题1:这类问题是否有标准/公认的解决方案或模式 我提出了以下想法:使用std::atomic来保护数据,并跟踪线程当前是在写入

我正在尝试实现以下功能:

  • 任意大小的数据类型的原子和无锁写或读修改写(在我的例子中,通常是一个最多有6个元素的浮点/整数向量)
  • 从上述数据类型读取的原子不会阻止写入线程。读操作可能被写操作阻止
用例:我正在尝试为CNC机器编写软件。电机的阶跃脉冲在软件中以实时线程的形式生成。此实时线程不断更新保持轴当前位置的变量。多个其他非实时线程可以读取该变量,例如显示当前位置

问题1:这类问题是否有标准/公认的解决方案或模式

我提出了以下想法:使用
std::atomic
来保护数据,并跟踪线程当前是在写入(通过检查最后一位)还是在读取开始后写入(通过增加写入时的值)

模板
无效读取\修改\写入(数据和数据,标准::原子和保护器,FN)
{
auto old_protector_value=protector.load();
做
{
//等待,直到没有其他线程正在写入
而(旧值%2!=0)
old_protector_value=protector.load();
//尝试获取写入权限
}而(!protector.compare_exchange_弱(old_protector_值,old_protector_值+1));
//写入数据
数据=fn(数据);
//解锁
保护器=旧保护器值+2;
};
模板
数据读取(常量数据和数据,标准::原子和保护器)
{
while(true)
{
uint64_t old_protector_value=protector.load();
//等待直到没有线程正在写入
而(旧值%2!=0)
old_protector_value=protector.load();
//读取数据
自动返回=数据;
//同时检查数据是否已更改
如果(旧保护器值==保护器)
返回ret;
}
}
问题2:上述代码是否线程安全并满足上述要求

问题3:可以改进吗

(我唯一能找到的理论问题是,如果计数器环绕,即在2次读取操作中执行精确的63×63写操作。如果没有更好的解决方案,我认为这个弱点是可以接受的)。


谢谢

严格来说,您的代码不是无锁的,因为您有效地使用了
protector
的LSB来实现自旋锁

您的解决方案看起来非常像一个。但是,实际读取操作
auto ret=data严格来说是一场数据竞赛。公平地说,在C++17中编写完全符合标准的seqlock是不可能的,因此我们必须等待C++20


可以扩展seqlock以使读取操作无锁,但代价是更高的内存使用率。其思想是拥有多个数据实例(我们称之为插槽),并且写入操作总是以循环方式写入下一个插槽。这允许读取操作从最后一个完全写入的插槽中读取。德米特里·维尤科夫(Dmitry Vyukov)在书中描述了他的方法。你可以看看我的,那是我生活的一部分。它还可以选择允许使用可配置数量的插槽进行无锁读取操作(尽管它与Vyukov在读卡器查找最后一个完全写入的插槽的方式上略有不同)。

我认为,如果您试图对任意大小的数据类型实现原子读/写,那么最终将使用锁,隐式或显式。原子访问和无锁访问是硬件的一项功能。@curiousguy当然。它不能同时是原子的和无锁的。@curiousguy你需要一个互斥或其他同步设备。硬件提供的一项功能是,无需额外显式使用同步,即可原子地修改内存块。例如,x86只允许对通常由指令处理的数据大小(如1、2、4、8字节)进行本机同步访问(通过锁前缀),尽管在硬件实现级别上,我认为它在64字节页面中进行同步。我很确定硬件中没有,比如说,256字节的同步功能。@guiousguy,就像mpoter的回答所说的,OP的代码实际上实现了一个软件自旋锁。因此,它不是无锁的,不管它是原子的。此外,值得一提的是,无锁仅指软件锁,因为硬件本身可能有某种较低级别的锁。@Anonymous1847 Oops我认为这里有一个英语问题!我将“原子访问和无锁访问”理解为“原子访问,以及无锁访问,(…)”,因此该短语将适用于这两种访问。但这意味着“访问既可以是原子的,也可以是无锁的”!!!我的错!非常感谢你。我不知道“序列锁”,这正是我所缺少的信息。不幸的是,要把它弄对有点棘手。如果您想自己实现seqlock,我建议您看一下Hans-J.Boehm的演示文稿:。不幸的是,我只能找到幻灯片的链接,HP页面上附带的技术报告的链接已经失效。
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;
  }
}