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
.
Cell
Cell
позволяет изменять значение без мутабельной ссылки, но только через копирование или замену.
Пример:
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
заменяет значение полностью.RefCell
RefCell
предоставляет динамическую мутабельность с проверкой правил владения во время выполнения.
Пример:
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. Они обеспечивают безопасность и гибкость, но требуют понимания их ограничений. Освоив их, вы сможете писать надёжные многопоточные программы. Далее мы рассмотрим работу с временем и процессами.
Выполните упражнение или переходите дальше!