我正在设计一种多线程算法,其中要求是读取共享变量的最新值。对变量的写入是原子的(使用比较和交换)。但是,读取不是原子的。
考虑以下示例:
//Global variable
int a = 10;
// Thread T1
void func_1() {
__sync_bool_compare_and_swap(&a, 10, 100);
}
// Thread T2
void func_2() {
int c = a;
/* Some Operations */
int b = a;
/* Some Operations */
}
如果在func_1中int b = a
之后(通过线程T2)执行代码__sync_bool_compare_and_swap
(通过线程T2),那么根据我的理解,仍然不能保证读取最新值“变量a“因为编译器可以缓存”a并使用“a”的旧值。
现在,为了避免这个问题,我将变量“volatile”声明如下:
volatile int a = 10;
// Thread T1
void func_1() {
__sync_bool_compare_and_swap(&a, 10, 100);
}
// Thread T2
void func_2() {
volatile int c = a;
/* Some Operations */
volatile int b = a;
/* Some Operations */
}
对于线程T1完成int b = a
之后线程T2执行__sync_bool_compare_and_swap
的情况,是否保证读取“a”的最新值?
缓存一致性和内存一致性模型如何影响原子写入后的易失性读取?
答案 0 :(得分:3)
volatile
关键字仅确保编译器不会将变量存储在寄存器中,而是在每次使用时从内存加载变量。它与运行它的系统的缓存或内存一致性模型无关。
答案 1 :(得分:0)
volatile
不会使读取操作成为原子。与(原子)写并发的非原子读取会导致未定义的行为。使用任何形式的原子读取,std::atomic
或内在函数。不要将volatile
用于任何形式的并发。
原子读取本身不保证该值将最新。在你的情况下,理论上线程T2可能永远不会读取100。该标准规定,实现(硬件,操作系统等)应尽最大努力使写入在有限时间内对其他线程可见。也许,在这里提出正式要求是不可能的。
通过额外的同步,您可以实现更多限制行为:
std::atomic<int> a = 10;
std::atomic<bool> done = false;
void func_1() {
int old = 10;
if (a.compare_exchange_strong(old, 100))
done.store(true);
}
void func_2() {
bool is_done = done.load();
int b = a.load();
assert(b == 100 || !is_done);
while (!done.load()); // May spin indefinitely long, but should not do that
assert(a.load() == 100);
}
实际上,要捕获那个简单的原子读取而不是最新的值,就必须在程序中放入足够的同步(以定义最新的),这样才能看起来工作正常。
答案 2 :(得分:0)
在您可能使用支持C ++和多个线程的所有平台上,从volatile兼容的int
读取将是原子的并将读取最新值。但是,C ++标准绝对不能保证。可能存在一些不起作用的平台,它可能无法与下一个CPU,编译器版本或操作系统版本一起使用。
理想情况下,使用保证提供原子性和可见性的东西。 C ++ - 11原子可能是最好的选择。编译器内在函数将是下一个最佳选择。如果您别无选择,只能使用volatile
,我建议您使用预处理程序测试来确认您所处的平台已知已足够并发出错误(使用#error
)如果没有。
请注意,在您可能使用的每个平台上,CPU内存缓存完全无关紧要,因为它们因硬件缓存一致性而变得不可见。在您可能使用的所有平台上,问题只是编译器优化,预取读取和发布的写入。