C++11 无锁同步、围栏和内存顺序(带有acquire语义的存储操作)

C++11 无锁同步、围栏和内存顺序(带有acquire语义的存储操作),c++11,atomic,lock-free,memory-fences,memory-barriers,C++11,Atomic,Lock Free,Memory Fences,Memory Barriers,我正在将一个在裸机上运行的项目迁移到linux,需要消除一些{disable,enable}\u调度程序调用。:) 因此,我需要在单个writer、多个reader场景中使用无锁同步解决方案,其中writer线程不能被阻止。我提出了以下解决方案,它不适合通常的acquire发布顺序: class RWSync { std::atomic<int> version; // incremented after every modification std::atomic_

我正在将一个在裸机上运行的项目迁移到linux,需要消除一些
{disable,enable}\u调度程序调用。:)

因此,我需要在单个writer、多个reader场景中使用无锁同步解决方案,其中writer线程不能被阻止。我提出了以下解决方案,它不适合通常的acquire发布顺序:

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);
  }
}
类RWSync{
std::原子版本;//每次修改后递增
std::atomic_bool无效;//写入期间为true
公众:
RWSync():版本(0),无效(0){}
模板无效同步(Fλ){
int当前版本;
做{
请{//等待对象有效
currentVersion=version.load(标准::内存\u顺序\u获取);
}while(无效的.load(std::memory_order_acquire));
lambda();
标准:原子线程围栏(标准:内存顺序顺序cst);
//检查是否有变化
}while(version.load(std::memory\u order\u acquire)!=当前版本
||无效。加载(标准::内存\u顺序\u获取));
}
void beginWrite(){
无效。存储(true,std::memory\u order\u released);
标准:原子线程围栏(标准:内存顺序顺序cst);
}
void endWrite(){
标准:原子线程围栏(标准:内存顺序顺序cst);
版本。获取\添加(1,标准::内存\顺序\发布);
无效。存储(错误,标准::内存\u顺序\u释放);
}
}
我希望意图是明确的:我将(非原子)有效负载的修改包装在
beginWrite/endWrite
之间,并仅在传递给
sync()
的lambda函数中读取有效负载

如您所见,我在
beginWrite()
中有一个原子存储,其中存储操作之后的写入不能在存储之前重新排序。我没有找到合适的例子,而且我在这个领域没有任何经验,所以我想确认一下它是否正常(通过测试进行验证也不容易)

  • 这段代码是否像我期望的那样自由竞争和工作

  • 如果我在每个原子操作中都使用std::memory\u order\u seq\u cst,我可以省略围栏吗?(即使是,我想性能会更差)

  • 我可以在endWrite()中放下围栏吗

  • 我可以在围栏里使用内存吗?我真的不明白其中的区别——我不清楚单一总订单的概念

  • 是否有简化/优化的机会


  • +一,。我很高兴接受任何更好的想法作为这个类的名称:)

    代码基本上是正确的

    您可以使用语义为“奇数值无效”的单个
    version
    变量,而不是两个原子变量(
    version
    invalid
    )。这被称为“顺序锁定”机制

    减少原子变量的数量可以简化很多事情:

    class RWSync {
        // Incremented before and after every modification.
        // Odd values mean that object in invalid state.
        std::atomic<int> version; 
    public:
      RWSync() : version(0) {}
      template<typename F> void sync(F lambda) {
        int currentVersion;
        do {
          currentVersion = version.load(std::memory_order_seq_cst);
          // This may reduce calls to lambda(), nothing more
          if(currentVersion | 1) continue;
    
          lambda();
    
          // Repeat until something changed or object is in an invalid state.
        } while ((currentVersion | 1) ||
            version.load(std::memory_order_seq_cst) != currentVersion));
      }
      void beginWrite() {
        // Writer may read version with relaxed memory order
        currentVersion = version.load(std::memory_order_relaxed);
        // Invalidation requires sequential order
        version.store(currentVersion + 1, std::memory_order_seq_cst);
      }
      void endWrite() {
        // Writer may read version with relaxed memory order
        currentVersion = version.load(std::memory_order_relaxed);
        // Release order is sufficient for mark an object as valid
        version.store(currentVersion + 1, std::memory_order_release);
      }
    };
    
    类RWSync{
    //在每次修改前后递增。
    //奇数值表示该对象处于无效状态。
    原子版本;
    公众:
    RWSync():版本(0){}
    模板无效同步(Fλ){
    int当前版本;
    做{
    currentVersion=version.load(标准::内存顺序顺序cst);
    //这可能会减少对lambda()的调用,仅此而已
    如果(当前版本| 1)继续;
    lambda();
    //重复此操作,直到发生更改或对象处于无效状态。
    }而((当前版本| 1)||
    加载(std::内存顺序cst)!=currentVersion);
    }
    void beginWrite(){
    //作者可以用宽松的记忆顺序阅读文本
    currentVersion=version.load(标准::内存\u顺序\u松弛);
    //失效需要顺序
    版本存储(当前版本+1,标准::内存顺序顺序cst);
    }
    void endWrite(){
    //作者可以用宽松的记忆顺序阅读文本
    currentVersion=version.load(标准::内存\u顺序\u松弛);
    //发布顺序足以将对象标记为有效
    版本存储(当前版本+1,标准::内存\订单\发布);
    }
    };
    
    请注意
    beginWrite()
    endWrite()
    中内存顺序的差异:

    • endWrite()
      确保所有以前的对象修改都已完成。使用释放内存顺序就足够了

    • beginWrite()
      确保在开始任何进一步修改对象之前,读取器将检测到对象处于无效状态。这样的担保需要顺序存储顺序。因为读卡器也使用seq_cst内存顺序

    至于fences,最好将它们合并到前面/后面的原子操作中:编译器知道如何快速生成结果


    原代码部分修改说明:

    1) 原子修改(如
    fetch\u add()
    )适用于同时修改(如另一个
    fetch\u add()
    )的情况。为确保正确性,此类修改使用内存锁定或其他非常耗时的特定于体系结构的东西

    原子分配(
    store()
    )不使用内存锁定,因此它比
    fetch\u add()
    便宜。您可以使用这种分配,因为在您的情况下不可能同时进行修改(读者不修改
    版本


    2) 与release acquire semantic不同,release acquire semantic可区分
    加载
    存储
    操作,顺序一致性(
    memory\u order\u seq\u cst
    )适用于每个原子访问,并提供这些访问之间的总顺序。

    接受的答案不正确。我猜代码应该类似于“currentVersion&1”,而不是“currentVersion | 1”。更微妙的错误是,读取器线程可以进入lambda(),然后,写入线程可以运行beginWrite()并将值写入non-at