首页 > 解决方案 > 如果由原子操作门控,非原子写入是否可以安全读取?

问题描述

我想创建一个“空”但可以稍后更新的复杂数据(此处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上看到它

似乎有效,但这并不意味着什么。我最关心的是,如果写入a并且肯定会被其他读为假b的线程看到。is_empty如果我保证:

这个可以吗?

我可以使用 anAtomicPtr代替,完全创建对象,然后交换指针,但我很好奇是否可以避免额外的间接。

标签: rustatomic

解决方案


您可能想使用Release 和 Acquire而不是SeqCst

释放 :

当与存储结合使用时,所有先前的操作都会在使用 Acquire(或更强)排序的任何加载该值之前排序。特别是,所有先前的写入对执行该值的获取(或更强)加载的所有线程都是可见的。

获得 :

当与加载相结合时,如果加载的值是由具有发布(或更强)排序的存储操作写入的,则所有后续操作都在该存储之后排序。特别是,所有后续加载都将看到在存储之前写入的数据。

改变这个:

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();
}

另请参阅docsnomicon


推荐阅读