IB-Rust-并发1
并发与并行的区别
- 并发(Concurrency):多个任务在时间上交替执行
- 并行(Parallelism):多个任务同时执行
进程与线程
- 在大部分 OS 里,代码运行在进程(process)中,OS同时管理多个进程
- 在程序里,各独立部分可以同时运行,运行这些独立部分的就是线程(thread)
- 多线程运行 可以提升性能表现 和 增加复杂性,但无法保障各线程的执行顺序
多线程导致的问题
- 竞争状态,线程以不一致的顺序访问数据或资源
- 死锁,两个线程彼此等待对方使用完所持有的资源,线程无法继续
- 只在某些情况下发生的 Bug,很难可靠地复制现象和修复
实现线程的方式
- 通过调用 OS 的 API 来创建线程:
1:1模型- 需要较小的运行时
- 语言自己实现的线程(绿色线程):
M:N模型(Go语言)- 需要更大的运行时
- Rust:需要权衡运行时的支持
- Rust 标准库仅提供
1:1模型的线程
Rust 如何创建线程
通过 thread::spawn 函数可以创建新线程,其参数是一个闭包(在新线程里运行的代码)
1 | use std::thread; |
通过 join Handle 来等待所有线程完成
thread::spawn 函数的返回值类型是 JoinHandle
JoinHandle 持有值的所有权,调用其 join 方法,会阻止当前运行线程的执行,直到 handle 所表示的线程终结
1 | let handle = thread::spawn(|| { |
使用 move 闭包
- move 闭包通常和
thread::spawn函数一起使用,它允许使用用其它线程的数据 - 创建线程时,把值的所有权从一个线程转移到另一个线程
1 | let v = vec![1, 2, 3]; |
多线程通信
消息传递
一种流行且能保证安全并发的技术是:消息传递,线程(或 Actor)通过彼此发送消息(数据)来进行通信
- Go 语言:不要用共享内存来通信,要用通信来共与享内存(与 Rust 相似)
- Rust:Channel(标准库提供)
Channel
Channel包含:发送端、接收端- 调用发送端的方法,发送数据
- 接收端会检查和接收到达的数据
- 如果发送端、接收端中任意一端被丢弃了,那么
Channel就又“关闭”了
创建 channel
- 使用
mpsc::channel函数来创建 Channelmpsc表示multiple producer,single consumer(多个生产者、一个消者)- 返回一个
tuple(元组):里面元素分别是发送端、接收端
- 使用
mpsc::sync_channel来创建带缓冲区的 channel- 入参为缓冲区大小,当缓冲区塞满时进行阻塞
1 | use std::sync::mpsc; |
总结
- 发送端的
send方法- 参数:想要发送的数据
- 返回:
Result<T,E>- 如果有问题(例如接收端已经被丢弃),就返回一个错误语
- 接收端的方法
recv方法:阻止当前线程执行,直到 Channel 中有值被送来- 一旦有值收到,就返回
Result<T,E> - 当发送端关闭,就会收到一个错误
- 一旦有值收到,就返回
try_recv方法:不会阻塞,- 立即返回
Result<T,E>:- 有数据达到:返回 Ok,里面包含着数据
- 否则,返回错误
- 通常会使用循环调用来检查
try_recv的结果
- 立即返回
课后习题1:实现多线程文件处理器
你需要编写一个多线程文件处理器,它从一个通道(channel)中接收文件路径,并在线程池中处理这些文件。文件处理的具体任务可以是读取文件内容并打印到控制台。你需要使用 Rust 的带缓冲区的 channel 来控制并发线程的数量,从而限制同时处理的文件数量。
- 具体要求
- 文件处理任务:
- 定义一个函数 process_file,该函数接受一个文件路径,读取文件内容,并将内容打印到控制台。
- 多线程控制:
- 创建一个带缓冲区的 channel,用于在主线程和工作线程之间传递文件路径。
- 使用多线程来实现文件处理的并发性,限制线程的并发数量(例如,最多同时处理 4 个文件)。
- 主线程作为生产者:
- 主线程负责向通道发送文件路径。假设我们有 10 个文件路径要处理。
- 工作线程作为消费者:
- 创建多个工作线程,每个线程从通道中接收文件路径,并调用 process_file 函数来处理文件。
1 | use std::path::PathBuf; |
课后习题2:使用 Channel 实现程序的优雅停止
在这道练习中,你需要编写一个多线程程序,该程序会创建多个工作线程,持续处理任务。在接收到停止信号时,所有工作线程应该优雅地停止工作,并确保所有未完成的任务都被处理完毕。
- 具体要求
- 你将使用 Rust 的 channel 来实现任务的调度和优雅停止机制。
- 工作线程:
- 创建一个工作线程池,工作线程从通道接收任务并处理。
- 工作线程应能够响应停止信号,并在完成当前任务后优雅地退出。
- 任务结构:
- 任务可以是简单的打印操作,模拟一些耗时工作,例如打印任务 ID 并暂停一段时间。
- 优雅停止:
- 通过发送一个特殊的停止信号,通知所有工作线程停止接收新的任务,并在完成当前任务后退出。
- 确保所有已接收的任务都被处理完毕。
- 主线程控制:
- 主线程应当能够发送任务,也能够在适当的时候发送停止信号,触发工作线程的优雅停止。
1 | use std::fmt; |
- Title: IB-Rust-并发1
- Author: Gabrielle
- Created at : 2025-06-16 16:28:03
- Updated at : 2025-06-16 22:06:25
- Link: https://zoella-w.github.io/2025/06/16/83-IB-Rust-并发1/
- License: This work is licensed under CC BY-NC-SA 4.0.