如果由原子操作门控,非原子写入是否可以安全读取?

Are non-atomic writes safe to read if gated by an atomic operation?

我想创建一个 "empty" 但可以复杂数据(此处为 ab)的对象,我可以稍后更新并设置原子标志以将其标记为非空,以便它可以在其他线程中使用。伪例子:

use std::sync::atomic::{AtomicBool, Ordering};
use std::cell::Cell;
use std::sync::Arc;
use std::{thread, time};

struct MyObject {
    is_empty: AtomicBool,
    a: Cell<u64>,
    b: Cell<u64>,
}

unsafe impl Sync for MyObject {}

fn main() {
    let obj = Arc::new(MyObject {
        is_empty: AtomicBool::new(true),
        a: Cell::new(0),
        b: Cell::new(0)
    });

    let thread_obj = obj.clone();
    let t = thread::spawn(move || {
        while thread_obj.is_empty.load(Ordering::SeqCst) {
            thread::sleep(time::Duration::from_millis(10));
        }

        println!("a is: {}", thread_obj.a.get());
        println!("b is: {}", thread_obj.b.get());
    });

    thread::sleep(time::Duration::from_millis(100));

    obj.a.set(42);
    obj.b.set(5);
    obj.is_empty.store(false, Ordering::SeqCst);

    t.join().unwrap();
}

Rust Playground

上查看

似乎 有用,但这并没有多大意义。我主要担心对 ab 的写入是否肯定会被其他将 is_empty 读取为 false 的线程可见。如果我保证:

这样可以吗?

我可以改用 AtomicPtr,完整地创建对象,并交换指针,但我很好奇是否可以避免额外的间接访问。

您可能想使用 Release and Acquire instead of SeqCst

Release :

When coupled with a store, all previous operations become ordered before any load of this value with Acquire (or stronger) ordering. In particular, all previous writes become visible to all threads that perform an Acquire (or stronger) load of this value.

Acquire :

When coupled with a load, if the loaded value was written by a store operation with Release (or stronger) ordering, then all subsequent operations become ordered after that store. In particular, all subsequent loads will see data written before the store.

改变这个:

fn main() {
    let obj = Arc::new(MyObject {
        is_empty: AtomicBool::new(true),
        a: Cell::new(0),
        b: Cell::new(0)
    });

    let thread_obj = obj.clone();
    let t = thread::spawn(move || {
        while thread_obj.is_empty.load(Ordering::SeqCst) {
            thread::sleep(time::Duration::from_millis(10));
        }

        println!("a is: {}", thread_obj.a.get());
        println!("b is: {}", thread_obj.b.get());
    });

    thread::sleep(time::Duration::from_millis(100));

    obj.a.set(42);
    obj.b.set(5);
    obj.is_empty.store(false, Ordering::SeqCst);

    t.join().unwrap();
}

进入:

fn main() {
    let obj = Arc::new(MyObject {
        is_empty: AtomicBool::new(true),
        a: Cell::new(0),
        b: Cell::new(0)
    });

    let thread_obj = obj.clone();
    let t = thread::spawn(move || {
        while thread_obj.is_empty.load(Ordering::Acquire){ // change
            thread::sleep(time::Duration::from_millis(10));
        }

        println!("a is: {}", thread_obj.a.get());
        println!("b is: {}", thread_obj.b.get());
    });

    thread::sleep(time::Duration::from_millis(100));

    obj.a.set(42);
    obj.b.set(5);
    obj.is_empty.store(false, Ordering::Release); //change

    t.join().unwrap();
}

另见 docs and nomicon