Многопоточное программирование на Rust
Каталог статей
Используйте модуль потока
Стандартная библиотека Rust обеспечивает поддержку потоков, которую можно использовать по прямой ссылке:
use std::thread;
Создать тему
Используйте метод spawn для создания потока. нравиться:
use std::thread;/* 引用线程模块 */
use std::time::Duration;
fn main() {
std::thread::spawn(thread_function);
loop {
thread::sleep(Duration::from_secs(1));/* sleep 1s */
println!("main thread running..");
}
}
fn thread_function(){
loop {
thread::sleep(Duration::from_secs(1));/* sleep 1s */
println!("demo thread running..");
}
}
Поток запускается автоматически после создания:
boys@server:~/rust_study/demo$ cargo run
Compiling demo v0.1.0 (/home/boys/rust_study/demo)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/demo`
main thread running..
demo thread running..
main thread running..
demo thread running..
main thread running..
demo thread running..
^C
boys@server:~/rust_study/demo$
Параметры передачи потока
Метод создания по умолчанию не может передавать параметры. Если вы хотите передать параметры в поток, вам необходимо использовать анонимную функцию в качестве параметра spawn.Анонимная функция также называется замыканием.
Закрытие (анонимная функция)
Основной синтаксис замыканий:
|param1, param2, ...| {
// 函数体
}
Преимущество использования замыканий заключается в том, что вы можете захватывать переменные вне функции и передавать их в тело функции.
По способу фиксации переменных его можно разделить на:
- получение стоимости
- Обычный захват ссылок
- изменяемый захват ссылок
получение стоимости
Способ захвата значения заключается в том, что право собственности на переменную передается замыканию и больше не может использоваться извне. нравиться:
fn main(){
let str = String::from("hello");
let closure_print_string = move ||{
/* move表明函数体内捕获的变量使用值捕获方式 */
println!("number = {}", str);/* 使用值捕获方式捕获外部的str */
};
closure_print_string();
println!("test for str: {}", str);/* 值捕获方式,str的所有权已经被转移到闭包内,这里无法再使用 */
}
Неизменяемый захват ссылок
Замыкания автоматически распознают захваченные типы переменных. Подтвердите метод захвата в зависимости от типа переменных, зафиксированных в теле функции.
Неизменяемые переменные фиксируются и используются в соответствии с методом неизменяемой ссылки, поэтому исходное значение переменной не может быть изменено, а к значению переменной можно только получить доступ. нравиться:
fn main(){
let str = String::from("hello");/* str是不可变 */
let closure = ||{
println!("str: {}", str);
// str.push_str("world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
};
closure();
println!("test for str: {}", str);
}
изменяемый захват ссылок
Превратив внешние переменные в изменяемые переменные, значение переменной можно изменить внутри тела функции замыкания.
fn main(){
let mut str = String::from("hello");/* str是不可变 */
let mut closure = ||{
println!("before push, str: {}", str);
str.push_str(" world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
};
closure();
println!("test for str: {}", str);
}
При этом тип замыкания также должен быть определен как изменяемый. Пока переменные внешней переменной фиксируются, они определяются как mut. нравиться:
fn main(){
let num = 123;
let mut str = String::from("hello");/* str是不可变 */
let mut closure = ||{
println!("num: {}", num);/* num不可变 */
println!("before push, str: {}", str);
str.push_str(" world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
};
closure();
println!("test for str: {}", str);
}
Передача параметров закрытия потока
Теперь вы можете использовать метод закрытия для передачи внешних параметров в поток.
нравиться:
fn main(){
let word = "what are words";
let thread_handle = std::thread::spawn(move ||{
println!("just print 'word': {}", word);
});
thread_handle.join().unwrap();
}
Здесь необходимо использовать метод захвата значения, чтобы передать замыканию право собственности на строковую ссылочную переменную str. Поскольку цикл объявления замыкания и текущей функции проверяется во время компиляции, обнаруживается, что замыкание может запуститься после завершения текущей функции, поскольку замыкание передается в другой поток.
closure may outlive the current function, but it borrows `word`, which is owned by the current function
Передавайте параметры более элегантно
Хотя внешние параметры могут передаваться в другой поток через замыкания, замыкания обычно используются для реализации относительно простых функций. Для потоков будут более сложные функции, поэтому более элегантный способ — использовать функцию для инкапсуляции потока, а затем использовать замыкание для передачи параметров для создания потока. нравиться:
use std::time::Duration;
struct ThreadParam{
thread_name: String
}
fn demo_thread(param: ThreadParam){
for _ in 0..3{
/* 使用下划线作为迭代变量,避免编译警告 */
std::thread::sleep(Duration::from_secs(1));
println!("thread's name: {}", param.thread_name);
}
}
fn main(){
let thread_arguments = ThreadParam{
thread_name: String::from("demo_thread")
};
let thread_handle = std::thread::spawn(move ||{
demo_thread(thread_arguments);
});
thread_handle.join().unwrap();/* 回收线程,防止主线程退出看不到线程效果 */
}
Переработка нити
Используйте метод соединения для повторного использования ресурсов потока. нравиться:
use std::thread;/* 引用线程模块 */
use std::time::Duration;
fn main() {
let thread_handle = std::thread::spawn(thread_function);
thread_handle.join().unwrap();
println!("thread exit.");
}
fn thread_function(){
let mut count = 1;
loop {
thread::sleep(Duration::from_secs(1));/* sleep 1s */
println!("demo thread run {} time..", count);
count = count + 1;
if count > 3 {
break;
}
}
}
boys@server:~/rust_study/demo$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/demo`
demo thread run 1 time..
demo thread run 2 time..
demo thread run 3 time..
thread exit.
Синхронизация потоков и связь
Rust предоставляет несколько встроенных механизмов для синхронизации потоков и связи:
- канал
- Мьютекс (блокировка мьютекса)
- Атомные типы
- Условная переменная
- Атомный подсчет ссылок (Arc)
- Барьер
канал канал
Каналы можно использовать только в сценариях std::sync::mpsc
с несколькими производителями и одним потребителем.
use std::sync::mpsc;
Пример использования:
fn main(){
let (sender, receiver) = std::sync::mpsc::channel();
let sender1 = sender.clone();/* 通过克隆2个sender传递到线程中 */
let sender2 = sender.clone();
let sender1_thread_handle = std::thread::spawn(move||{
std::thread::sleep(std::time::Duration::from_secs(1));
sender1.send("hello").unwrap();
});
let sender2_thread_handle = std::thread::spawn(move||{
std::thread::sleep(std::time::Duration::from_secs(2));
sender2.send("bye").unwrap();
});
let mut is_connected = false;
loop {
let recv_data = receiver.recv().unwrap();
if recv_data == "hello"{
println!("recv 'hello', connected");
is_connected = true;
}
if is_connected == true{
if recv_data == "bye"{
println!("recv 'bye', disconnected");
break;
}
}
}
sender1_thread_handle.join().unwrap();
sender2_thread_handle.join().unwrap();
}
мьютекс блокировка мьютекса
Модуль импорта:
use std::sync::Mutex;
Мьютекс в Rust — это универсальный тип. Он определяется как std::sync::Mutex<T>
тип T
общих данных, подлежащих защите. Тип данных будет автоматически определен во время инициализации.
Ниже приведен пример использования Mutex для выполнения взаимоисключающего доступа к целочисленным переменным:
use std::sync::{
Mutex, Arc};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(42));
let thread1_data = Arc::clone(&data);
let handle1 = thread::spawn(move || {
// 使用 thread1_data 进行操作
let mut value = thread1_data.lock().unwrap();
*value += 1;
println!("thread1 Value: {}", *value);
});
let thread2_data = Arc::clone(&data);
let handle2 = thread::spawn(move || {
// 使用 thread2_data 进行操作
let value = thread2_data.lock().unwrap();
println!("thread2 Value: {}", *value);
});
handle1.join().unwrap();
handle2.join().unwrap();
}
Тип атомарного подсчета ссылок Arc используется здесь для реализации нескольких потоков, совместно владеющих блокировкой мьютекса.
Барьерный Забор
В Rust вы можете использовать Barrier
типы для реализации ограничения потоков. Barrier
Позволяет группе потоков достичь определенной точки и затем продолжить выполнение одновременно.
Пример:
use std::sync::{
Arc, Barrier};
use std::thread;
fn main() {
let barrier = Arc::new(Barrier::new(3)); // 创建一个包含3个参与者的栅栏
for i in 0..3 {
/* 创建3个线程 */
/* 使用Arc原子引用计数将barrier共享给所有线程 */
let barrier_clone = Arc::clone(&barrier);
thread::spawn(move || {
println!("Thread {} before barrier", i);
barrier_clone.wait(); // 所有线程都到达此处后会同时继续执行
println!("Thread {} after barrier", i);
});
}
thread::sleep(std::time::Duration::from_secs(2)); // 等待足够的时间以确保所有线程完成
println!("Main thread");
}
Атомарные типы Атомные типы
Обычно используемые атомарные типы в Rust:
- AtomicBool атомарный логический тип
- Атомный целочисленный тип AtomicI32
- Тип атомарного указателя AtomicPtr
Атомарные типы являются неделимыми типами, а операции над переменными атомарного типа неделимы, поэтому их часто используют в сценариях многопоточного параллелизма, чтобы избежать состояний гонки.
Простой пример использования:
use std::sync::atomic::{
AtomicI32, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let atomic_counter = Arc::new(AtomicI32::new(0));
let mut handles = vec![];/* 使用vec保存线程句柄,回收资源使用 */
for _ in 0..5 {
let counter = Arc::clone(&atomic_counter);
let handle = thread::spawn(move || {
for _ in 0..1_0 {
counter.fetch_add(1, Ordering::SeqCst);
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", atomic_counter.load(Ordering::SeqCst));
}
Создаются пять потоков для увеличения атомарной переменной в 10 раз, а выходной результат равен 50. Если атомарные переменные не используются и не предусмотрена защита взаимного исключения, полученные результаты неизвестны.
boys@server:~/rust_study/demo$ cargo run
Compiling demo v0.1.0 (/home/boys/rust_study/demo)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/demo`
Final value: 50