std::thread: Управление потоками
2. std::sync: Синхронизация между потоками
3. std::cell: Управление мутабельностью
Практические советы
Пример: Комбинирование модулей
Упражнение
Заключение
В этом разделе мы разберём инструменты стандартной библиотеки Rust для работы с потоками и синхронизацией: std::thread, std::sync и std::cell. Эти модули позволяют писать параллельный код, безопасно обмениваться данными между потоками и управлять мутабельностью в сложных сценариях. Лекция подойдёт как новичкам, желающим понять основы многопоточности, так и опытным разработчикам, стремящимся освоить тонкости Rust. Мы рассмотрим каждый модуль с примерами, нюансами и практическими советами.
std::thread: Управление потокамиМодуль std::thread предоставляет средства для создания и управления потоками выполнения (threads). Потоки позволяют выполнять задачи параллельно, используя многоядерность современных процессоров.
Основные функции:
spawn: Создание нового потока.join: Ожидание завершения потока.sleep: Приостановка текущего потока.Пример: запуск простого потока:
use std::thread;
use std::time::Duration;
fn main() {
// Создаём поток
let handle = thread::spawn(|| {
for i in 1..5 {
println!("Поток: {}", i);
thread::sleep(Duration::from_millis(100));
}
});
// Главный поток
for i in 1..5 {
println!("Главный: {}", i);
thread::sleep(Duration::from_millis(100));
}
// Ожидаем завершения потока
handle.join().unwrap();
}
Комментарии:
spawn принимает замыкание и возвращает JoinHandle.join блокирует текущий поток, пока вызванный не завершится, и возвращает Result.sleep приостанавливает выполнение на заданное время.Заметка: Без join главный поток может завершиться раньше, и дочерний поток будет прерван.
std::sync: Синхронизация между потокамиМодуль std::sync предоставляет инструменты для безопасного обмена данными между потоками. В Rust строгая модель владения предотвращает гонки данных, но для общей мутабельности нужны дополнительные механизмы, такие как Mutex, RwLock и Arc.
Mutex: Взаимное исключениеMutex (Mutual Exclusion) обеспечивает доступ к данным только одному потоку одновременно.
Пример: счётчик в нескольких потоках:
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn({
let counter = counter.lock().unwrap();
|| {
*counter += 1; // Ошибка: counter уже заблокирован
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Итог: {}", *counter.lock().unwrap());
}
Ошибка в примере: Этот код не сработает, так как counter.lock() нельзя использовать вне потока напрямую. Вот исправленная версия с Arc:
Arc: Атомарный счётчик ссылокArc (Atomic Reference Counting) позволяет безопасно делить данные между потоками.
Исправленный пример:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Итог: {}", *counter.lock().unwrap()); // 10
}
Комментарии:
Arc клонируется для каждого потока, обеспечивая владение.Mutex::lock возвращает MutexGuard, который автоматически разблокируется при выходе из области видимости.RwLock: Чтение/записьRwLock позволяет множественным потокам читать данные одновременно, но только одному записывать.
Пример:
use std::sync::{RwLock, Arc};
use std::thread;
fn main() {
let data = Arc::new(RwLock::new(42));
let mut handles = vec![];
// Потоки для чтения
for _ in 0..3 {
let data = Arc::clone(&data);
let handle = thread::spawn(move || {
let value = data.read().unwrap();
println!("Чтение: {}", *value);
});
handles.push(handle);
}
// Поток для записи
let data_writer = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut value = data_writer.write().unwrap();
*value = 100;
println!("Запись: {}", *value);
});
handles.push(handle);
for handle in handles {
handle.join().unwrap();
}
}
Комментарии:
read() позволяет множественный доступ на чтение.write() блокирует всех, пока запись не завершена.Предупреждение: Неправильное использование lock или write может привести к взаимной блокировке (deadlock). Всегда проверяйте логику.
std::cell: Управление мутабельностьюМодуль std::cell предоставляет типы для управления мутабельностью в однопоточных сценариях, где стандартные правила владения слишком строгие: Cell и RefCell.
CellCell позволяет изменять значение без мутабельной ссылки, но только через копирование или замену.
Пример:
use std::cell::Cell;
fn main() {
let value = Cell::new(5);
println!("Начальное: {}", value.get()); // 5
value.set(10);
println!("Новое: {}", value.get()); // 10
}
Комментарии:
get работает только для типов, реализующих Copy.set заменяет значение полностью.RefCellRefCell предоставляет динамическую мутабельность с проверкой правил владения во время выполнения.
Пример:
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
{
let mut borrowed = data.borrow_mut();
borrowed.push(4);
}
println!("Данные: {:?}", data.borrow());
}
Комментарии:
borrow даёт неизменяемую ссылку, borrow_mut — изменяемую.borrow_mut одновременно).Заметка: Cell и RefCell не потокобезопасны. Для потоков используйте Mutex или RwLock.
std::thread: Всегда вызывайте join, если результат потока важен.std::sync: Используйте Arc с Mutex для общих данных, а RwLock — если чтение преобладает.std::cell: Применяйте Cell для простых типов, а RefCell — для сложных структур.Программа с потоками, счётчиком и мутабельностью:
use std::sync::{Arc, Mutex};
use std::thread;
use std::cell::RefCell;
fn main() {
let counter = Arc::new(Mutex::new(0));
let data = Arc::new(RefCell::new(vec![]));
let mut handles = vec![];
for i in 0..5 {
let counter = Arc::clone(&counter);
let data = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
data.borrow_mut().push(i);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Счётчик: {}", *counter.lock().unwrap());
println!("Данные: {:?}", data.borrow());
}
Этот пример использует thread для параллелизма, Mutex для счётчика и RefCell для списка.
Напишите программу, которая:
std::thread.Arc и RwLock для общего числа.read после завершения потоков.Cell для хранения количества выполненных потоков.Подсказка: Используйте join для синхронизации.
Модули std::thread, std::sync и std::cell дают полный контроль над параллелизмом и мутабельностью в Rust. Они обеспечивают безопасность и гибкость, но требуют понимания их ограничений. Освоив их, вы сможете писать надёжные многопоточные программы. Далее мы рассмотрим работу с временем и процессами.
Выполните упражнение или переходите дальше!