C++11 无锁同步、围栏和内存顺序(带有acquire语义的存储操作)
我正在将一个在裸机上运行的项目迁移到linux,需要消除一些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_
{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