Безопасно ли читать не-атоми c, если они управляются операцией атоми c? - PullRequest
1 голос
/ 21 февраля 2020

Я хочу создать объект, который является «пустым», но может содержать сложные данные (здесь a и b), которые я могу обновить позже и установить флаг atomi c, чтобы пометить его как непустой, чтобы это может быть использовано в других темах. Псевдо пример:

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 как ложное. Если я гарантирую:

  • все записи в a и b происходят до установки флага
  • , ни один поток не читает a и b до установки флага

это нормально?

Я мог бы вместо этого использовать AtomicPtr, создать объект полностью и поменять местами указатель, но мне любопытно, могу ли я избежать дополнительной косвенности .

1 Ответ

1 голос
/ 21 февраля 2020

Возможно, вы захотите использовать Release и Acquire вместо SeqCst

Release:

В сочетании с магазином все предыдущие операции становятся упорядоченными до любой загрузки этого значения с упорядочением Приобретения (или более сильным). В частности, все предыдущие записи становятся видимыми для всех потоков, которые выполняют загрузку этого значения (или более сильную).

Получение:

При соединении с нагрузкой, если записанное загруженное значение было записано в результате операции с упорядочением по выпуску (или более строгим) все последующие операции упорядочиваются после этого хранилища. В частности, все последующие загрузки будут видеть данные, записанные перед хранилищем.

Измените это:

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

Также см. документы и номикон .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...