Arc&Mutex - IsuiGit/borodaedu GitHub Wiki
A thread-safe reference-counting pointer. Arc stands for Atomically Reference Counted.
The type Arc<T> provides shared ownership of a value of type T, allocated in the heap. Invoking clone on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc, while increasing a reference count. When the last Arc pointer to a given allocation is destroyed, the value stored in that allocation (often referred to as “inner value”) is also dropped.
Shared references in Rust disallow mutation by default, and Arc is no exception: you cannot generally obtain a mutable reference to something inside an Arc. If you need to mutate through an Arc, use Mutex, RwLock, or one of the Atomic types.
Note: This type is only available on platforms that support atomic loads and stores of pointers, which includes all platforms that support the `std` crate but not all those which only support alloc. This may be detected at compile time using #[cfg(target_has_atomic = "ptr")].
Потокобезопасный указатель подсчета ссылок «Arc» означает «Atomically Reference Counted» (атомарный подсчет ссылок).
use std::sync::Arc;
use std::thread;
let five = Arc::new(5);
for _ in 0..10 {
let five = Arc::clone(&five);
thread::spawn(move || {
println!("{five:?}");
});
}При работе с многопоточными программами на Rust крайне важно защитить общие данные от одновременного доступа, который может привести к гонке данных (data race) и непредсказуемому поведению. Для управления доступа к общим ресурсам язык Rust предоставляет такой инструмент как мьютекс (mutex - сокращение от "mutual exclusion" - "взаимное исключение"). Мьютекс представляет примитив синхронизации в Rust, который помогает защитить общие данные в нескольких потоках и который гарантирует, что только один поток может одновременно получить доступ к защищенным данным, предотвращая гонку данных.
Мьютекс реализован как смарт-указатель Mutex, который присутствует в стандартной библиотеке Rust в модуле std::sync. Для управления доступом Mutex предоставляет метод lock. Когда поток получает блокировку с помощью метода lock(), это гарантирует, что никакие другие потоки не смогут одновременно изменять или получать доступ к защищенным данным. Этот механизм синхронизации необходим для предотвращения гонок за данными и обеспечения упорядоченного выполнения параллельного кода.
Кроме того, мьютекс обеспечивает защиту от потенциальных утечек ресурсов, автоматически снимая блокировку, когда блокировка мьютекса (обычно представленная в виде переменной) выходит за пределы области действия. Этот механизм автоматического снятия упрощает управление блокировками и снижает вероятность случайного возникновения взаимоблокировок или других проблем синхронизации в программах Rust.
use std::sync::Mutex;
fn main() {
let data = Mutex::new(22); // создаем мьютекс для доступа к общему ресурсу - числу 22
{
let mut guard = data.lock().unwrap(); // блокируем мьютекс, получаем доступ к ресурсу
*guard *= 2; // различные действия с ресурсом
} // после завершения области действия блокировки Mutex автоматически разблокируется
println!("Data: {:?}", data.lock().unwrap()); // Data: 44
}Допустим, у нас есть программа, которая каждую итерацию своей работы обновляет информацию о состоянии системы и хранит эти данные в структуре. Следовательно, у нас есть основной цикл работы программы, в котором происходит взаимодействие пользователя с интерфейсом и отдельный процесс обновления данных, который должен происходить каждые n секунд, вне зависимости от действий пользователя.
Пример такой функции:
cli.rs
use std::{
...
sync::{Arc, Mutex},
sync::atomic::{AtomicBool, Ordering}
};
...
pub fn run(sys: machine::SystemInfo){
let mut _sys = Arc::new(Mutex::new(sys)); // создание указателя на изменяемую структуру sys, обернутую в mutex, для устранения "гонки" между вызовами
let run = Arc::new(AtomicBool::new(true)); // создание атомарной логической переменной для управления процессом (если run = false, то процесс сбора данных будет остановлен
let task = { // создаем "задачу" - участок кода, который будет отправлен в отдельный поток выполнения
let _sys = Arc::clone(&_sys);
move || {
let mut sys = _sys.lock().unwrap();
sys.update();
}
};
// передаем управление над "задачей" обновления данных в корневую структуру на запуск в поток выполнения
core::run_mut_task(task, Arc::clone(&run));
...
}core.rs
use std::{
sync::Arc,
thread,
time::Duration,
sync::atomic::{AtomicBool, Ordering},
};
...
pub fn run_mut_task<F>(mut task: F, run: Arc<AtomicBool>) // создадим функцию, которая будет принимать в себя в качестве аргумента другую функцию - "задачу", и состояние выполнения (переменную run, созданную в cli)
where
F: FnMut() + Send + 'static,
{
thread::spawn(move || { // создаём поток выполнения
while run.load(Ordering::SeqCst) { // получаем состояние переменной run. Если она всё ещё true - выполняем задачу и отправляем поток в сон на 1 секунду
task();
thread::sleep(Duration::from_secs(1));
}
println!("\nsystem::run_mut_task ended succesefully\n"); // если поток выполнения окончен - выводим об этом сообщение в консоль
});
}