Многопоточное программирование на Rust

Многопоточное программирование на 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:

  1. AtomicBool атомарный логический тип
  2. Атомный целочисленный тип AtomicI32
  3. Тип атомарного указателя 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

Supongo que te gusta

Origin blog.csdn.net/qq_41790078/article/details/132568479
Recomendado
Clasificación