如果由原子操作门控,非原子写入是否可以安全读取?
Are non-atomic writes safe to read if gated by an atomic operation?
我想创建一个 "empty" 但可以复杂数据(此处为 a
和 b
)的对象,我可以稍后更新并设置原子标志以将其标记为非空,以便它可以在其他线程中使用。伪例子:
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();
}
上查看
它似乎 有用,但这并没有多大意义。我主要担心对 a
和 b
的写入是否肯定会被其他将 is_empty
读取为 false 的线程可见。如果我保证:
- 对
a
和 b
的所有写入都发生在设置标志之前
- 在设置标志之前没有线程读取
a
和 b
这样可以吗?
我可以改用 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();
}
我想创建一个 "empty" 但可以复杂数据(此处为 a
和 b
)的对象,我可以稍后更新并设置原子标志以将其标记为非空,以便它可以在其他线程中使用。伪例子:
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();
}
上查看
它似乎 有用,但这并没有多大意义。我主要担心对 a
和 b
的写入是否肯定会被其他将 is_empty
读取为 false 的线程可见。如果我保证:
- 对
a
和b
的所有写入都发生在设置标志之前 - 在设置标志之前没有线程读取
a
和b
这样可以吗?
我可以改用 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();
}