英文:
Memory ordering questions on given example?
问题
以下是翻译的内容:
-
可以
read_and_write_non_atomic_memory_1_before_lock();
被重新排序放在
do_something_under_lock
调用之前吗(例如,如果它被内联)?可以,直到具有原子的
Acquire
(因为Acquire
阻止重新排序到其之前的位置)或同一内存上的非原子操作(因为它会破坏单线程逻辑)。 -
可以
read_and_write_non_atomic_memory_1_before_lock();
被重新排序放在原子
Acquire
之后吗?可以,因为原子
Acquire
不会阻止编译器重新排序到其之后的位置。 -
可以
read_and_write_non_atomic_memory_1_before_lock();
被重新排序放在
read_and_write_non_atomic_memory_1_inside_lock();
之后吗?
不可以,因为这些操作影响相同的内存,因此它们应该按照它们在代码中出现的顺序发生,否则单线程执行逻辑可能会破坏。
-
可以
read_and_write_non_atomic_memory_1_inside_lock();
被重新排序放在原子
Release
之后吗?不可以,因为原子
Release
阻止它。 -
可以
read_and_write_non_atomic_memory_1_after_lock();
被重新排序放在
Release
之前吗?可以,因为原子
Release
不会阻止重新排序到其之前的位置。 -
可以
read_and_write_non_atomic_memory_1_after_lock();
被重新排序放在
read_and_write_non_atomic_memory_1_inside_lock();
之前吗?
不可以,因为这些操作影响相同的内存,因此它们应该按照它们在代码中出现的顺序发生,否则单线程执行逻辑可能会破坏。
-
可以
read_and_write_non_atomic_memory_2_before_lock();
被重新排序放在
do_something_under_lock
调用之前吗(例如,如果它被内联)?可以,直到具有原子的
Acquire
(因为Acquire
阻止重新排序到其之前的位置)或同一内存上的非原子操作(因为它会破坏单线程逻辑)。 -
可以
read_and_write_non_atomic_memory_2_before_lock();
被重新排序放在
read_and_write_non_atomic_memory_1_before_lock();
之前吗?
可以,因为这些操作不会影响相同的内存,并且它们之间没有原子的
Acquire
或Release
。 -
可以
read_and_write_non_atomic_memory_2_before_lock();
被重新排序放在原子
Acquire
之后吗?可以,因为原子
Acquire
不会阻止编译器重新排序到其之后的位置。 -
可以
read_and_write_non_atomic_memory_2_before_lock();
被重新排序放在原子
Release
之后吗?不可以,因为原子
Release
阻止它。 -
可以
read_and_write_non_atomic_memory_2_after_lock();
被重新排序放在
Release
之前吗?可以,因为原子
Release
不会阻止重新排序到其之前的位置。 -
可以
read_and_write_non_atomic_memory_2_after_lock();
被重新排序放在
Acquire
之前吗?不可以,因为原子
Acquire
阻止重新排序到其之前的位置。 -
可以
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
被重新排序放在
do_something_under_lock
调用之前吗(例如,如果它被内联)?可以,直到具有原子的
Acquire
(因为Acquire
阻止重新排序到其之前的位置)或对相同原子内存的操作(因为它会破坏单线程逻辑)。 -
可以
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
被重新排序放在原子
Acquire
之后吗?可以,因为原子
Acquire
不会阻止编译器重新排序到其之后的位置。 -
可以
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
被重新排序放在
some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
之后吗?
不可以,因为这些操作影响相同的内存,因此它们应该按照它们在代码中出现的顺序发生,否则单线程执行逻辑可能会破坏。
-
可以
some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
被重新排序放在原子
Release
之后吗?不可以,因为原子
Release
阻止它。 -
可以
some_atomic_counter_1.fetch_add(3, Ordering::Relaxed);
被重新排序放在
Release
之前吗?可以,因为原
英文:
I have questions about memory reorderings in this code
fn do_something_under_lock(
is_locked: &AtomicBool,
some_atomic_counter_1: &AtomicUsize,
some_atomic_counter_2: &AtomicUsize,
) {
read_and_write_non_atomic_memory_1_before_lock();
read_and_write_non_atomic_memory_2_before_lock();
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
let mut succeeded_acquire;
loop {
succeeded_acquire = is_locked
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok();
if succeeded_acquire {
read_and_write_non_atomic_memory_1_inside_lock();
some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
is_locked.store(false, Ordering::Release);
break;
}
}
read_and_write_non_atomic_memory_1_after_lock();
read_and_write_non_atomic_memory_2_after_lock();
some_atomic_counter_1.fetch_add(3, Ordering::Relaxed);
some_atomic_counter_2.fetch_add(3, Ordering::Relaxed);
}
<hr>
Here I will be giving a list of questions and answers based on my understanding, but I would like confirmation if I am correct:
-
Can
read_and_write_non_atomic_memory_1_before_lock();
be reordered to place before
do_something_under_lock
call(like if it was inlined)?
> Yes, up until the point where we have atomicAcquire
(becauseAcquire
prevents reordering to places before itself) or non-atomic operation on same memory(because it would break single threaded logic) -
Can
read_and_write_non_atomic_memory_1_before_lock();
be reordered to place after atomic
Acquire
?
> Yes, because atomicAcquire
doesn't prevent compiler from reordering to places after it -
Can
read_and_write_non_atomic_memory_1_before_lock();
be reordered to place after
read_and_write_non_atomic_memory_1_inside_lock();
?
> No, because these operations affect same memory, so they should happen in same order, as they were met in code, because otherwise logic of single threaded execution may break -
Can
read_and_write_non_atomic_memory_1_inside_lock();
be reordered to place after atomic
Release
?
> No, because atomicRelease
prevents it -
Can
read_and_write_non_atomic_memory_1_after_lock();
be reordered to place before
Release
?
> Yes, because atomicRelease
doesn't prevent reordering to places before itself -
Can
read_and_write_non_atomic_memory_1_after_lock();
be reordered to place before
read_and_write_non_atomic_memory_1_inside_lock();
?
> No, because these operations affect same memory, so they should happen in same order, as they were met in code, because otherwise logic of single threaded execution may break -
Can
read_and_write_non_atomic_memory_2_before_lock();
be reordered to place before
do_something_under_lock
call(like if it was inlined)?
> Yes, up until the point where we have atomicAcquire
(becauseAcquire
prevents reordering to places before itself) or non-atomic operation on same memory(because it would break single threaded logic) -
Can
read_and_write_non_atomic_memory_2_before_lock();
be reordered to place before
read_and_write_non_atomic_memory_1_before_lock();
?
> Yes, because these operations don't affect same memory and don't have atomicAcquire
orRelease
between them -
Can
read_and_write_non_atomic_memory_2_before_lock();
be reordered to place after atomic
Acquire
?
> Yes, because atomicAcquire
doesn't prevent compiler from reordering to places after it -
Can
read_and_write_non_atomic_memory_2_before_lock();
be reordered to place after atomic
Release
?
> No, because atomicRelease
prevents it -
Can
read_and_write_non_atomic_memory_2_after_lock();
be reordered to place before
Release
?
> Yes, because atomicRelease
doesn't prevent reordering to places before itself -
Can
read_and_write_non_atomic_memory_2_after_lock();
be reordered to place before
Acquire
?
> No, because atomicAcquire
prevents reordering to places before itself -
Can
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
be reordered to place before
do_something_under_lock
call(like if it was inlined)?
> Yes, up until the point where we have atomicAcquire
(becauseAcquire
prevents reordering to places before itself) or operation on same atomic memory(because it would break single threaded logic) -
Can
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
be reordered to place after atomic
Acquire
?
> Yes, because atomicAcquire
doesn't prevent compiler from reordering to places after itself -
Can
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
be reordered to place after
some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
?
> No, because these operations affect same memory, so they should happen in same order, as they were met in code, because otherwise logic of single threaded execution may break -
Can
some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
be reordered to place after atomic
Release
?
> No, because atomicRelease
prevents it -
Can
some_atomic_counter_1.fetch_add(3, Ordering::Relaxed);
be reordered to place before
Release
?
> Yes, because atomicRelease
doesn't prevent reordering to places before itself -
Can
some_atomic_counter_1.fetch_add(3, Ordering::Relaxed);
be reordered to place before
some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
?
> No, because these operations affect same memory, so they should happen in same order, as they were met in code, because otherwise logic of single threaded execution may break -
Can
some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
be reordered to place before
do_something_under_lock
call(like if it was inlined)?
> Yes, up until the point where we have atomicAcquire
(becauseAcquire
prevents reordering to places before itself) or operation on same atomic memory(because it would break single threaded logic) -
Can
some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
be reordered to place before
some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
?
> Yes, because these operations don't affect same memory and don't have atomicAcquire
orRelease
between them -
Can
some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
be reordered to place after atomic
Acquire
?
> Yes, because atomicAcquire
doesn't prevent compiler from reordering to places after itself -
Can
some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
be reordered to place after atomic
Release
?
> No, because atomicRelease
prevents it -
Can
some_atomic_counter_2.fetch_add(3, Ordering::Relaxed);
be reordered to place before
Release
?
> Yes, because atomicRelease
doesn't prevent reordering to places before itself -
Can
some_atomic_counter_2.fetch_add(3, Ordering::Relaxed);
be reordered to place before
Acquire
?
> No, because atomicAcquire
prevents reordering to places before itself -
What would happen if we changed
Acquire
toRelaxed
in linesucceeded_acquire = is_locked .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) .is_ok();
?
> Compiler wouldn't tell CPU to load to local core cache actual memory changes from global memory and would work on potentially old memory, causing race inread_and_write_non_atomic_memory_1_inside_lock();
-
What would happen if we changed
Release
toRelaxed
in lineis_locked.store(false, Ordering::Release);
?
> Compiler wouldn't tell CPU to push local memory changes from local core cache to global memory and other cores would work on potentially old memory, causing race inread_and_write_non_atomic_memory_1_inside_lock();
答案1
得分: 1
在#3和#6中存在一些微妙之处,涉及对同一非原子对象的多次访问。
首先,显然 Rust尚未定义内存模型,所以这些问题可能无法以完全的权威性来回答。但是,我的理解是,就所有意图而言,它与C++的内存模型相匹配,该模型根本不是按照“重新排序”的术语来定义的,而是按照允许您推断某些程序行为是否可能的抽象排序约束来定义的。因此,在这个意义上,询问一个访问是否可以与另一个重新排序并不是一个严格定义的问题。
现在,在#3和#6中,真正的情况是,在当前线程内,对各种non_atomic_memory
对象的访问将被观察到就像按程序顺序一样。例如,read_and_write_non_atomic_memory_1_inside_lock()
将读取由read_and_write_non_atomic_memory_1_before_lock()
写入的值。在这个意义上,它们不能被重新排序。
但是,从另一个线程的角度来看,它们可以被重新排序,或者更糟。事实上,如果任何其他线程与这些访问同时观察这个变量,那么程序就存在数据竞争,其整个行为是未定义的。(在Rust中,除非有unsafe
代码来打败借用检查器,否则其他线程实际上不可能这样做。) 因此,如果编译器可以以某种其他方式在当前线程内获得相同的效果,它就没有义务生成与您的read_and_write()
函数执行的访问相匹配的实际内存加载和存储。例如,before_lock()
写入的值实际上可能永远不会存储到内存中,而是缓存在一个寄存器中,然后由inside_lock()
读取。或者它们可以以某种任意复杂的方式混淆。
在另一个线程可以访问变量而不会引发数据竞争的情况下,内存中的值不必是“正确的”,这在您发布的代码中的任何时候都不会发生。
英文:
There is a subtlety in #3 and #6, involving multiple accesses to the same non-atomic object.
First, apparently Rust does not yet have a defined memory model, so it may be that these questions cannot be answered with complete authority. However, my understanding is that for all intents and purposes, it matches the C++ memory model, which is not defined in terms of "reordering" at all, but in terms of abstract ordering constraints that allow you to deduce whether certain program behaviors are possible or not. So in that sense, asking whether one access can be reordered with another is not formally a well-defined question.
Now, in #3 and #6, what is true is that within the current thread, the accesses to the various non_atomic_memory
objects will be observed as if in program order. For example, read_and_write_non_atomic_memory_1_inside_lock()
will read the value that was written by read_and_write_non_atomic_memory_1_before_lock()
. In that sense, they cannot be reordered.
However, from the point of view of another thread, they can be reordered, or worse. In fact, if any other thread is observing this variable concurrently with these accesses, the program has a data race and its entire behavior is undefined. (And in Rust, it should actually be impossible for any other thread to do so, unless there is unsafe
code in play to defeat the borrow checker.) So the compiler is under no obligation to generate actual memory loads and stores matching the accesses that your read_and_write()
functions do, if it can get the same effect within the current thread in some other way. For instance, the value written by the before_lock()
write might never actually be stored to memory, but instead cached in a register which is then used by the inside_lock()
read. Or they could be scrambled up in some arbitrarily complicated fashion.
The value in memory doesn't have to be "correct" until such time as another thread could access the variable without causing a data race, which doesn't happen at any point within the code you posted.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论