Rust 练习册 10:多线程基础与并发安全

并发编程是现代软件开发中的重要主题,它允许程序同时执行多个任务以提高性能和响应性。Rust 通过其所有权系统和类型系统,在编译时就保证了线程安全,避免了数据竞争等常见并发问题。今天我们就来深入学习 Rust 中的多线程编程基础。

什么是多线程编程?

多线程编程允许程序同时执行多个线程,每个线程都是独立的执行路径。在 Rust 中,线程是轻量级的,由操作系统调度,可以并行执行以提高程序性能。

项目中的示例代码

让我们先看看项目中的示例代码:

rust 复制代码
use std::{thread, time::Duration};

#[test]
fn it_works() {
    let duration = Duration::from_millis(3000);
    thread::spawn(move || {
        thread::sleep(duration);
    });

    assert_eq!(duration.as_millis(), 3000);
    println!("{:?}", duration);
}

// fn inner_fn(vref: &mut Vec<u32>) {
//     std::thread::spawn(move || {
//         vref.push(1);
//     });
// }
#[test]
fn inner_it_works() {
    let mut v = vec![1, 2, 3];
    // inner_fn(&mut v);
}

在这个示例中,我们使用 thread::spawn 创建了一个新线程,该线程会休眠 3 秒。注意 move 关键字的使用,它将 duration 变量的所有权转移到新线程中。

被注释掉的代码展示了 Rust 如何防止数据竞争:我们不能简单地将可变引用传递给新线程,因为这会违反 Rust 的借用规则。

基本线程操作

创建线程

rust 复制代码
use std::thread;
use std::time::Duration;

fn basic_thread_creation() {
    // 创建一个新线程
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 在主线程中执行
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    // 等待新线程完成
    handle.join().unwrap();
}

使用 move 关键字

rust 复制代码
use std::thread;

fn move_keyword_example() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

move 关键字将闭包环境中使用的值的所有权转移到新线程中。

线程间通信

使用通道(Channels)

rust 复制代码
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn channel_example() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

多生产者通道

rust 复制代码
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn multiple_producers_example() {
    let (tx, rx) = mpsc::channel();
    
    let tx1 = tx.clone();
    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];

        for val in vals {
            tx1.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    thread::spawn(move || {
        let vals = vec![
            String::from("more"),
            String::from("messages"),
            String::from("for"),
            String::from("you"),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    for received in rx {
        println!("Got: {}", received);
    }
}

共享状态并发

使用互斥锁(Mutex)

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread;

fn mutex_example() {
    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!("Result: {}", *counter.lock().unwrap());
}

使用原子类型

rust 复制代码
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;

fn atomic_example() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            counter.fetch_add(1, Ordering::SeqCst);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", counter.load(Ordering::SeqCst));
}

线程安全的智能指针

Arc(原子引用计数)

rust 复制代码
use std::sync::Arc;
use std::thread;

fn arc_example() {
    let data = Arc::new(vec![1, 2, 3, 4]);
    
    let mut handles = vec![];
    
    for i in 0..4 {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            println!("Thread {}: {:?}", i, data_clone);
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
}

实际应用示例

并行计算

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread;

fn parallel_computation_example() {
    let numbers: Vec<i32> = (1..=1000).collect();
    let sum = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    
    let chunk_size = numbers.len() / 4;
    
    for chunk in numbers.chunks(chunk_size) {
        let chunk = chunk.to_vec();
        let sum_clone = Arc::clone(&sum);
        
        let handle = thread::spawn(move || {
            let chunk_sum: i32 = chunk.iter().sum();
            let mut total = sum_clone.lock().unwrap();
            *total += chunk_sum;
        });
        
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Sum: {}", *sum.lock().unwrap());
}

生产者-消费者模式

rust 复制代码
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn producer_consumer_example() {
    let (tx, rx) = mpsc::channel();
    
    // 生产者线程
    let producer = thread::spawn(move || {
        for i in 0..10 {
            println!("Producing item {}", i);
            tx.send(i).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });
    
    // 消费者线程
    let consumer = thread::spawn(move || {
        for received in rx {
            println!("Consuming item {}", received);
            thread::sleep(Duration::from_millis(750));
        }
    });
    
    producer.join().unwrap();
    consumer.join().unwrap();
}

错误处理与线程

处理线程中的 panic

rust 复制代码
use std::thread;

fn panic_handling_example() {
    let handle = thread::spawn(|| {
        panic!("Oops!");
    });

    match handle.join() {
        Ok(_) => println!("Thread completed successfully"),
        Err(_) => println!("Thread panicked"),
    }
}

返回结果的线程

rust 复制代码
use std::thread;

fn result_thread_example() {
    let handle = thread::spawn(|| -> Result<i32, &'static str> {
        // 一些可能失败的操作
        Ok(42)
    });

    match handle.join() {
        Ok(Ok(result)) => println!("Result: {}", result),
        Ok(Err(err)) => println!("Error: {}", err),
        Err(_) => println!("Thread panicked"),
    }
}

最佳实践

1. 合理使用线程

rust 复制代码
use std::thread;
use std::time::Duration;

// 好的做法:为计算密集型任务使用线程
fn good_thread_usage() {
    let handle = thread::spawn(|| {
        // CPU 密集型计算
        let result: u128 = (1..1_000_000).map(|x| x * x).sum();
        result
    });
    
    // 在等待时做其他工作
    println!("Doing other work while waiting...");
    
    let result = handle.join().unwrap();
    println!("Result: {}", result);
}

// 避免:为简单任务创建过多线程
fn avoid_excessive_threads() {
    // 不好的做法:为每个简单操作创建线程
    // let handles: Vec<_> = (0..1000)
    //     .map(|i| {
    //         thread::spawn(move || i * 2)
    //     })
    //     .collect();
}

2. 正确处理所有权

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread;

fn ownership_example() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3, 4, 5]));
    
    let mut handles = vec![];
    
    // 正确:克隆 Arc 来共享所有权
    for i in 0..3 {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let mut vec = data_clone.lock().unwrap();
            vec.push(i);
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Final data: {:?}", *data.lock().unwrap());
}

与项目代码的深入分析

让我们回到原始项目代码,深入分析其中的概念:

rust 复制代码
use std::{thread, time::Duration};

#[test]
fn it_works() {
    let duration = Duration::from_millis(3000);
    thread::spawn(move || {
        thread::sleep(duration);
    });

    assert_eq!(duration.as_millis(), 3000);
    println!("{:?}", duration);
}

这段代码展示了:

  1. 使用 move 关键字将 duration 的所有权转移到新线程
  2. 在新线程中使用 thread::sleep 模拟耗时操作
  3. 主线程继续执行而不等待子线程完成

被注释的代码揭示了 Rust 的一个重要特性:

rust 复制代码
// fn inner_fn(vref: &mut Vec<u32>) {
//     std::thread::spawn(move || {
//         vref.push(1);
//     });
// }

这段代码无法编译,因为:

  1. &mut Vec<u32> 是一个可变引用
  2. 将可变引用移动到新线程会违反 Rust 的借用规则
  3. 这会可能导致数据竞争

正确的做法是使用 Arc<Mutex<T>>

rust 复制代码
use std::sync::{Arc, Mutex};
use std::thread;

fn correct_shared_mutable_state() {
    let mut v = vec![1, 2, 3];
    let shared_v = Arc::new(Mutex::new(v));
    
    let shared_v_clone = Arc::clone(&shared_v);
    let handle = thread::spawn(move || {
        let mut vec = shared_v_clone.lock().unwrap();
        vec.push(1);
    });
    
    handle.join().unwrap();
    
    println!("Vector: {:?}", *shared_v.lock().unwrap());
}

总结

Rust 的多线程编程通过其所有权系统提供了强大的安全保障:

  1. thread::spawn 创建新线程
  2. move 关键字转移所有权到新线程
  3. 通道(Channels)提供线程间安全通信
  4. MutexArc 允许安全的共享状态
  5. 原子类型提供无锁并发操作

关键要点:

  • Rust 在编译时防止数据竞争
  • 所有权系统确保线程安全
  • 通过 move 关键字明确所有权转移
  • 使用标准库提供的同步原语处理共享状态

通过合理使用这些特性,我们可以编写出既高效又安全的并发程序。

相关推荐
披着羊皮不是狼3 小时前
多用户博客系统搭建(1):表设计+登录注册接口
java·开发语言·springboot
zzzyyy5385 小时前
C++之vector容器
开发语言·c++
WX-bisheyuange6 小时前
基于Spring Boot的教师个人成果管理系统的设计与实现
java·spring boot·后端
chxii6 小时前
spring boot 获取HTTP 请求参数
spring boot·后端·http
xunyan62347 小时前
面向对象(上)-封装性的引入
java·开发语言
还算善良_7 小时前
XML签名
xml·java·开发语言
梅梅绵绵冰7 小时前
xml方式实现AOP
xml·java·开发语言
桦说编程8 小时前
Guava 迭代器增强类介绍
java·后端·设计模式
235169 小时前
【JVM】Java为啥能跨平台?JDK/JRE/JVM的关系?
java·开发语言·jvm·spring boot·后端·spring·职场和发展