关于给定示例的内存排序问题?

huangapple go评论76阅读模式
英文:

Memory ordering questions on given example?

问题

以下是翻译的内容:

  1. 可以

    read_and_write_non_atomic_memory_1_before_lock();
    

    被重新排序放在 do_something_under_lock 调用之前吗(例如,如果它被内联)?

    可以,直到具有原子的 Acquire(因为 Acquire 阻止重新排序到其之前的位置)或同一内存上的非原子操作(因为它会破坏单线程逻辑)。

  2. 可以

    read_and_write_non_atomic_memory_1_before_lock();
    

    被重新排序放在原子 Acquire 之后吗?

    可以,因为原子 Acquire 不会阻止编译器重新排序到其之后的位置。

  3. 可以

    read_and_write_non_atomic_memory_1_before_lock();
    

    被重新排序放在

    read_and_write_non_atomic_memory_1_inside_lock();
    

    之后吗?

    不可以,因为这些操作影响相同的内存,因此它们应该按照它们在代码中出现的顺序发生,否则单线程执行逻辑可能会破坏。

  4. 可以

    read_and_write_non_atomic_memory_1_inside_lock();
    

    被重新排序放在原子 Release 之后吗?

    不可以,因为原子 Release 阻止它。

  5. 可以

    read_and_write_non_atomic_memory_1_after_lock();
    

    被重新排序放在 Release 之前吗?

    可以,因为原子 Release 不会阻止重新排序到其之前的位置。

  6. 可以

    read_and_write_non_atomic_memory_1_after_lock();
    

    被重新排序放在

    read_and_write_non_atomic_memory_1_inside_lock();
    

    之前吗?

    不可以,因为这些操作影响相同的内存,因此它们应该按照它们在代码中出现的顺序发生,否则单线程执行逻辑可能会破坏。

  7. 可以

    read_and_write_non_atomic_memory_2_before_lock();
    

    被重新排序放在 do_something_under_lock 调用之前吗(例如,如果它被内联)?

    可以,直到具有原子的 Acquire(因为 Acquire 阻止重新排序到其之前的位置)或同一内存上的非原子操作(因为它会破坏单线程逻辑)。

  8. 可以

    read_and_write_non_atomic_memory_2_before_lock();
    

    被重新排序放在

    read_and_write_non_atomic_memory_1_before_lock();
    

    之前吗?

    可以,因为这些操作不会影响相同的内存,并且它们之间没有原子的 AcquireRelease

  9. 可以

    read_and_write_non_atomic_memory_2_before_lock();
    

    被重新排序放在原子 Acquire 之后吗?

    可以,因为原子 Acquire 不会阻止编译器重新排序到其之后的位置。

  10. 可以

    read_and_write_non_atomic_memory_2_before_lock();
    

    被重新排序放在原子 Release 之后吗?

    不可以,因为原子 Release 阻止它。

  11. 可以

    read_and_write_non_atomic_memory_2_after_lock();
    

    被重新排序放在 Release 之前吗?

    可以,因为原子 Release 不会阻止重新排序到其之前的位置。

  12. 可以

    read_and_write_non_atomic_memory_2_after_lock();
    

    被重新排序放在 Acquire 之前吗?

    不可以,因为原子 Acquire 阻止重新排序到其之前的位置。

  13. 可以

    some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
    

    被重新排序放在 do_something_under_lock 调用之前吗(例如,如果它被内联)?

    可以,直到具有原子的 Acquire(因为 Acquire 阻止重新排序到其之前的位置)或对相同原子内存的操作(因为它会破坏单线程逻辑)。

  14. 可以

    some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
    

    被重新排序放在原子 Acquire 之后吗?

    可以,因为原子 Acquire 不会阻止编译器重新排序到其之后的位置。

  15. 可以

    some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
    

    被重新排序放在

    some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
    

    之后吗?

    不可以,因为这些操作影响相同的内存,因此它们应该按照它们在代码中出现的顺序发生,否则单线程执行逻辑可能会破坏。

  16. 可以

    some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
    

    被重新排序放在原子 Release 之后吗?

    不可以,因为原子 Release 阻止它。

  17. 可以

    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:

  1. 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 atomic Acquire(because Acquire prevents reordering to places before itself) or non-atomic operation on same memory(because it would break single threaded logic)

  2. Can

    read_and_write_non_atomic_memory_1_before_lock();
    

    be reordered to place after atomic Acquire?
    > Yes, because atomic Acquire doesn't prevent compiler from reordering to places after it

  3. 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

  4. Can

    read_and_write_non_atomic_memory_1_inside_lock();
    

    be reordered to place after atomic Release?
    > No, because atomic Release prevents it

  5. Can

    read_and_write_non_atomic_memory_1_after_lock();
    

    be reordered to place before Release?
    > Yes, because atomic Release doesn't prevent reordering to places before itself

  6. 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

  7. 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 atomic Acquire(because Acquire prevents reordering to places before itself) or non-atomic operation on same memory(because it would break single threaded logic)

  8. 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 atomic Acquire or Release between them

  9. Can

    read_and_write_non_atomic_memory_2_before_lock();
    

    be reordered to place after atomic Acquire?
    > Yes, because atomic Acquire doesn't prevent compiler from reordering to places after it

  10. Can

    read_and_write_non_atomic_memory_2_before_lock();
    

    be reordered to place after atomic Release?
    > No, because atomic Release prevents it

  11. Can

    read_and_write_non_atomic_memory_2_after_lock();
    

    be reordered to place before Release?
    > Yes, because atomic Release doesn't prevent reordering to places before itself

  12. Can

    read_and_write_non_atomic_memory_2_after_lock();
    

    be reordered to place before Acquire?
    > No, because atomic Acquire prevents reordering to places before itself

  13. 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 atomic Acquire(because Acquire prevents reordering to places before itself) or operation on same atomic memory(because it would break single threaded logic)

  14. Can

    some_atomic_counter_1.fetch_add(1, Ordering::Relaxed);
    

    be reordered to place after atomic Acquire?
    > Yes, because atomic Acquire doesn't prevent compiler from reordering to places after itself

  15. 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

  16. Can

    some_atomic_counter_1.fetch_add(2, Ordering::Relaxed);
    

    be reordered to place after atomic Release?
    > No, because atomic Release prevents it

  17. Can

    some_atomic_counter_1.fetch_add(3, Ordering::Relaxed);
    

    be reordered to place before Release?
    > Yes, because atomic Release doesn't prevent reordering to places before itself

  18. 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

  19. 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 atomic Acquire(because Acquire prevents reordering to places before itself) or operation on same atomic memory(because it would break single threaded logic)

  20. 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 atomic Acquire or Release between them

  21. Can

    some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
    

    be reordered to place after atomic Acquire?
    > Yes, because atomic Acquire doesn't prevent compiler from reordering to places after itself

  22. Can

    some_atomic_counter_2.fetch_add(1, Ordering::Relaxed);
    

    be reordered to place after atomic Release?
    > No, because atomic Release prevents it

  23. Can

    some_atomic_counter_2.fetch_add(3, Ordering::Relaxed);
    

    be reordered to place before Release?
    > Yes, because atomic Release doesn't prevent reordering to places before itself

  24. Can

    some_atomic_counter_2.fetch_add(3, Ordering::Relaxed);
    

    be reordered to place before Acquire?
    > No, because atomic Acquire prevents reordering to places before itself

  25. What would happen if we changed Acquire to Relaxed in line

    succeeded_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 in read_and_write_non_atomic_memory_1_inside_lock();

  26. What would happen if we changed Release to Relaxed in line

    is_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 in read_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.

huangapple
  • 本文由 发表于 2023年7月3日 11:34:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76601704.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定