Rust multi-threaded programming

Rust multi-threaded programming

Use the thread module

The rust standard library provides thread-related support, which can be used by direct reference:

use std::thread;

Create thread

Use the spawn method to create a thread. like:

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..");
    }
}

The thread runs automatically after creation:

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$ 

Thread passing parameters

The default spawn method cannot pass parameters. If you want to pass parameters to the thread, you need to use an anonymous function as the parameter of spawn. The anonymous function is also called a closure.

Closure (anonymous function)

Basic syntax of closures:

|param1, param2, ...| {
    
    
    // 函数体
}

The advantage of using closures is that you can capture variables outside the function and pass them into the function body.

According to the way of capturing variables, it can be divided into:

  • value capture
  • Ordinary reference capture
  • mutable reference capture

value capture

The way value is captured is that the ownership of the variable is transferred to the closure and can no longer be used externally. like:

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的所有权已经被转移到闭包内,这里无法再使用 */
}

Immutable reference capture

Closures automatically recognize the captured variable types. Confirm the capture method based on the type of variables captured in the function body.

Immutable variables are captured and used according to the immutable reference method, so the original variable value cannot be modified, and the variable value can only be accessed. like:

fn main(){
    
    
    let str = String::from("hello");/* str是不可变 */
    let closure = ||{
    
    
        println!("str: {}", str);
        // str.push_str("world");/* 不可变的变量按不可变引用方式捕获,此处修改了变量值会报错 */
    };
    closure();
    println!("test for str: {}", str);
}

mutable reference capture

By turning external variables into mutable variables, the value of the variable can be modified within the closure function body.

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);
}

At the same time, the type of closure must also be defined as mutable. As long as external variable variables are captured, they are defined as mut. like:

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);
}

Thread closure passing parameters

Now you can use the closure method to pass external parameters to the thread.

like:

fn main(){
    
    
    let word = "what are words";
    let thread_handle = std::thread::spawn(move ||{
    
    
        println!("just print 'word': {}", word);
    });
    thread_handle.join().unwrap();
}

The value capture method must be used here to transfer the ownership of the string reference variable str to the closure. Because the declaration cycle of the closure and the current function is checked at compile time, it is found that the closure may run after the current function ends because the closure is passed to another thread.

closure may outlive the current function, but it borrows `word`, which is owned by the current function

Pass parameters more elegantly

Although external parameters can be passed to another thread through closures, closures are generally used to implement relatively simple functions. For threads, there will be more complex functions, so a more elegant way is to use a function to encapsulate a thread, and then use a closure to pass the parameters to create the thread. like:

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();/* 回收线程,防止主线程退出看不到线程效果 */
}

Recycle thread

Use the join method to recycle thread resources. like:

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.

Thread synchronization and communication

Rust provides several built-in mechanisms for thread synchronization and communication:

  • channel
  • Mutex (mutex lock)
  • Atomic Types
  • Condition Variable
  • Atomic reference counting (Arc)
  • Barrier

channel channel

Channels can only be used in multi-producer, single-consumer scenarios. The module is std::sync::mpscmpsc, which is the abbreviation of "Multi-Producer, Single-Consumer", that is, multi-producer and single consumer.

use std::sync::mpsc;

Usage example:

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();
}

mutex mutex lock

Import module:

use std::sync::Mutex;

Mutex in Rust is a generic type. It is defined as std::sync::Mutex<T>where Tis the type of shared data to be protected. The data type will be automatically determined during initialization.

The following is an example of using Mutex to perform mutually exclusive access to integer variables:

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();
}

The Arc atomic reference counting type is used here to realize multiple threads sharing ownership of the mutex lock.

Barrier Fence

In Rust, you can use Barriertypes to implement thread fences. BarrierAllows a group of threads to all reach a point and then continue execution at the same time.

Example:

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");
}

Atomic Types Atomic Types

Commonly used atomic types in Rust are:

  1. AtomicBool atomic Boolean type
  2. AtomicI32 atomic integer type
  3. AtomicPtr atomic pointer type

Atomic types are indivisible types, and operations on atomic type variables are indivisible. Therefore, they are often used in multi-threaded concurrency scenarios to avoid race conditions.

Simple usage example:

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));
}

Five threads are created to increment the atomic variable 10 times, and the output result is 50. If atomic variables are not used and no mutual exclusion protection is provided, the results obtained are unknown.

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

Guess you like

Origin blog.csdn.net/qq_41790078/article/details/132568479