gpt4 book ai didi

c++ - 如何在C++ 11普通存储(导出)和装入(导入)障碍(栅栏)中表达?

转载 作者:行者123 更新时间:2023-12-01 14:40:24 28 4
gpt4 key购买 nike

以下代码实现了一些无锁(且无原子!)的线程间通信,这些通信需要使用存储和加载内存屏障,但是C++ 11 release-acquire语义不适当,也不保证正确性。实际上,该算法暴露了对发布获取语义的某种反转的需求,即,表示没有发生某种操作而不是发生了某种操作。

volatile bool valid=true;
volatile uint8_t blob[1024] = {/*some values*/};

void zero_blob() {
valid=false;
STORE_BARRIER;
memset(blob,0,1024);
}

int32_t try_get_sum(size_t index_1, size_t index_2) {
uint8_t res = blob[index_1] + blob[index_2];
LOAD_BARRIER;
return valid ? res : -1;
}

我只需使用 native 内存屏障即可在所有硬件体系结构上使此代码正确无误在Intel上,这里不需要内存障碍,在Sparc(RMO)membar #StoreStore和membar #LoadLoad上,在PowerPC lwsync上都不需要。因此没什么大不了的,并且代码是使用存储和负载屏障的典型示例。现在,假设我不想将“blob”转换为 std::atomic对象,我应该使用哪种C++ 11构造来使代码正确,因为它将使“blob”成为保护对象,而使“valid”成为 protected 对象,而相反。
对我来说,将变量“有效”转换为 std::atomic对象是可以的,但是没有障碍可以保证正确性。为了清楚起见,让我们考虑以下代码:
volatile std::atomic<bool> valid{true};
volatile uint8_t blob[1024] = {/*some values*/};

void zero_blob() {
valid.store(false, std::memory_order_release);
memset(blob,0,1024);
}

int32_t try_get_sum(size_t index_1, size_t index_2) {
uint8_t res = blob[index_1] + blob[index_2];
return valid.load(std::memory_order_acquire) ? res : -1;
}

代码不正确,因为障碍放置在错误的位置,因此写入“blob”的操作可能先于写入“valid”的操作,和/或从“valid”的加载可能先于从“blob”的加载。我认为,为了处理此类构造,C++ 11提供了 std::atomic_thread_fence,其代码应为:
volatile std::atomic<bool> valid{true};
volatile uint8_t blob[1024] = {/*some values*/};

void zero_blob() {
valid.store(false, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
memset(blob,0,1024);
}

int32_t try_get_sum(size_t index_1, size_t index_2) {
uint8_t res = blob[index_1] + blob[index_2];
std::atomic_thread_fence(std::memory_order_acquire);
return valid.load(std::memory_order_relaxed); ? res : -1;
}

不幸的是,C++ 11说:

A release fence A synchronizes with an acquire fence B if there exist atomic operations X and Y, both operating on some atomic object M, such that A is sequenced before X, X modifies M, Y is sequenced before B, and Y reads the value written by X or a value written by any side effect in the hypothetical release sequence X would head if it were a release operation.



明确指出 std::atomic_thread_fence应该放置在原子对象上操作的相对侧。

后期编辑

请在下面找到更多可用的示例:
volatile uint64_t clock=1;
volatile uint8_t blob[1024] = {/*some values*/};

void update_blob(uint8_t vals[1024]) {
clock++;
STORE_BARRIER;
memcpy(blob,vals,1024);
STORE_BARRIER;
clock++;
}

int32_t try_get_sum(size_t index_1, size_t index_2) {
uint64_t snapshot = clock;
if(snapshot & 0x1) {
LOAD_BARRIER;
uint8_t res = blob[index_1] + blob[index_2];
LOAD_BARRIER;
if(snapshot == clock)
return res;
}
return -1;
}

最佳答案

根据memory_order article的说法,为了保守起见,您需要在存储后使用memory_order_release和在加载之前使用memory_order_acquire(两个原子变量都应使用)。

所以:

 std::atomic<int> var;

// Writer
// something important <happens-before> writing 42 in the writer thread
var.store(42, std::std::memory_order_release);

// Reader
auto result = var.load(std::std::memory_order_acquire);
if (result == 42) {
// transitively, as the result's new value is observed, the "something important" is here too
}

通常,根据需要实现的效果以及目标体系结构支持的内容,您可以不那么保守地进行此操作。

通常,您更喜欢 std::atomic_flag 而不是 std::atomic<bool>,因为与后者不同,前者保证是无锁的。

最后-为什么不从互斥锁保护的关键部分开始,甚至更好地通过无锁环形缓冲区将更新推送给使用者,以使它们不共享任何内容?

关于c++ - 如何在C++ 11普通存储(导出)和装入(导入)障碍(栅栏)中表达?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57808919/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com