Я довольно новичок в Rust.Я получил высшее образование по специальности «Компьютерная инженерия» 4 года назад, и я не забываю обсуждать (и понимать) атомарные операции в своем курсе по операционным системам.Однако после выпуска я работаю в основном на языках высокого уровня, где мне не нужно было заботиться о вещах низкого уровня, таких как атомарность.Теперь, когда я вхожу в Rust, я пытаюсь вспомнить, как работает большая часть этого материала.
В настоящее время я пытаюсь понять исходный код библиотеки hibitset ,в частности atomic.rs .
Этот модуль задает тип AtomicBitSet
, который соответствует типу BitSet
из lib.rs, но с использованием атомарных значений и операций.Насколько я понимаю, «атомарная операция» - это операция, которая гарантированно не будет прервана другим потоком;любой «load» или «store» с одним и тем же значением должен будет дождаться завершения операции, прежде чем продолжить.Из этого определения следует, что «атомарное значение» - это значение, операции которого являются полностью атомарными.AtomicBitSet
использует AtomicUsize
, который является оболочкой usize
, где все методы полностью атомарны.Однако AtomicBitSet
определяет несколько операций, которые, по-видимому, не являются атомарными (add
и remove
), и есть одна атомарная операция: add_atomic
.Глядя на add
против add_atomic
, я не могу точно сказать, в чем разница.
Вот add
(дословно):
/// Adds `id` to the `BitSet`. Returns `true` if the value was
/// already in the set.
#[inline]
pub fn add(&mut self, id: Index) -> bool {
use std::sync::atomic::Ordering::Relaxed;
let (_, p1, p2) = offsets(id);
if self.layer1[p1].add(id) {
return true;
}
self.layer2[p2].store(self.layer2[p2].load(Relaxed) | id.mask(SHIFT2), Relaxed);
self.layer3
.store(self.layer3.load(Relaxed) | id.mask(SHIFT3), Relaxed);
false
}
Этот метод вызывает load()
и store()
напрямую.Я предполагаю, что тот факт, что он использует Ordering::Relaxed
, делает этот метод неатомарным, потому что другой поток, выполняющий то же самое с другим индексом, мог бы засорять эту операцию.
Вот add_atomic
(дословно):
/// Adds `id` to the `AtomicBitSet`. Returns `true` if the value was
/// already in the set.
///
/// Because we cannot safely extend an AtomicBitSet without unique ownership
/// this will panic if the Index is out of range.
#[inline]
pub fn add_atomic(&self, id: Index) -> bool {
let (_, p1, p2) = offsets(id);
// While it is tempting to check of the bit was set and exit here if it
// was, this can result in a data race. If this thread and another
// thread both set the same bit it is possible for the second thread
// to exit before l3 was set. Resulting in the iterator to be in an
// incorrect state. The window is small, but it exists.
let set = self.layer1[p1].add(id);
self.layer2[p2].fetch_or(id.mask(SHIFT2), Ordering::Relaxed);
self.layer3.fetch_or(id.mask(SHIFT3), Ordering::Relaxed);
set
}
Этот метод использует fetch_or
вместо прямого вызова load
и store
, что, как я предполагаю, и делает этот метод атомарным.
Но почемуиспользование Ordering::Relaxed
все еще позволяет это считать атомарным?Я понимаю, что отдельные операции "или" являются атомарными, но полный метод может быть запущен одновременно с другим потоком.Разве это не повлияло бы?
Кроме того, почему такой тип предоставляет неатомарные методы?Это только для производительности?Это кажется мне запутанным.Если бы я выбрал AtomicBitSet
вместо BitSet
, потому что он будет использоваться более чем одним потоком, я бы, вероятно, хотел бы использовать только атомарные операции над ним.Если бы я не сделал, я бы не использовал его.Правильно?
Я бы тоже хотел получить пояснение к комментарию внутри add_atomic
.Как-то это не имеет смысла для меня.Разве неатомарная версия все еще должна заботиться об этом?Кажется, что эти два метода эффективно делают одно и то же, просто с разными уровнями атомарности.
Мне бы очень хотелось, чтобы какая-то помощь обернула голову вокруг атома.Я думаю Я понимаю порядок после прочтения этого и этого , но оба все еще используют понятия, которые я не понимаю.Когда они говорят о том, что один поток «видит» что-то из другого, что это значит?Когда говорится, что последовательно согласованные операции имеют одинаковый порядок «во всех потоках», что это вообще означает?Меняет ли процессор по-разному порядок команд для разных потоков?