线程
Thread线程
Concurrent Programming: 并发编程
Parallel Programming: 并行编程
创建线程
thread::spawn创建
使用
thread::spawn来创建一个新的线程,参数为一个闭包函数
use std::thread;引入
// 引入模块
use std::thread;
use std::time::Duration;
fn main() {
// 创建线程
// 接收一个闭包函数
let t = thread::spawn(|| {
println!("start thread");
thread::sleep(Duration::from_secs(10));
println!("end thread");
});
// 等待线程完成
t.join().unwrap();
}
thread::Builder创建
thread::Builder创建的线程可以设置线程名字以及栈大小
use std::thread;
use std::time::Duration;
fn main() {
let name = "thread-name".to_string();
// 设置线程的名称和栈大小
let builder = thread::Builder::new()
.name(name)
.stack_size(4 * 1024 * 1024);
// 创建线程
// 接收一个闭包函数
let t = builder.spawn(move || {
println!("start thread");
thread::sleep(Duration::from_secs(10));
println!("end thread");
}).unwrap();
// 等待线程完成
t.join().unwrap();
}
查看当前线程名称
thread::current().name().unwrap()获取当前线程名
thread::spawn中查看当前线程名称
thread::spawn没有线程名,获取线程名会报错
use std::thread;
fn main() {
// 创建线程
// 接收一个闭包函数
let t = thread::spawn(print_name);
// 等待线程完成
t.join().unwrap();
}
fn print_name() {
println!("thread name: {}", thread::current().name().unwrap());
}
thread '<unnamed>' panicked at src/main.rs:14:58:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at src/main.rs:10:14:
called `Result::unwrap()` on an `Err` value: Any { .. }
thread::Builder中查看当前线程名称
可以获取线程名
use std::thread;
fn main() {
let name = "thread-name-1".to_string();
// 设置线程的名称和栈大小
let builder = thread::Builder::new()
.name(name)
.stack_size(4 * 1024 * 1024);
// 创建线程
// 接收一个闭包函数
let t = builder.spawn(print_name).unwrap();
// 等待线程完成
t.join().unwrap();
}
fn print_name() {
println!("thread name: {}", thread::current().name().unwrap());
}
thread name: thread-name-1
查看线程
Mac使用把
ps hH p替换为ps -M -p
# 查看进程
ps -ef | grep -v grep| grep <rust-name>
# 查看进程对应的线程
ps hH p <PID>
# 一行查询
ps -ef | grep -v grep| grep <rust-name>| awk '{print $2}'| xargs ps hH p
# 直接显示线程数量
ps -ef | grep -v grep| grep <rust-name>| awk '{print $2}'| xargs ps hH p| wc -l
# 查看进程对应的线程树
-a:显示每个程序的完整命令行参数。
-p:显示每个进程的 PID。
-c:强制展开所有相同的子树,不使用精简显示方式。
-n:按进程号(PID)排序,而不是默认的按进程名称排序。
-u:显示进程所有者(UID)或 UID 转换信息。
-h:高亮显示当前进程或祖先进程。
-H PID:高亮显示指定 PID 的进程及其祖先。
-g:显示进程组 ID(PGID)。
-l:不截断长行,用长行格式显示结构。
-A:使用 ASCII 字符绘制树状结构。
-U:使用 UTF-8 字符绘制树状结构。
--compact-not:关闭进程树的精简显示。
ps -ef | grep -v grep| grep $S_TNAME | awk '{print $2}'| xargs pstree -a -u -p -g
# 检测
ps --ppid <pid> -o pid,ppid,cmd
案列
use std::thread;
use std::time::Duration;
fn main() {
let mut handel = vec![];
// 创建10个线程
for i in 1..=10 {
let t = thread::spawn(move || {
println!("a: {}", i);
thread::sleep(Duration::from_secs(10));
}
);
handel.push(t);
}
for h in handel {
h.join().unwrap();
}
}
# 一个11个线程,一个主线程 + 10个子线程
ps -ef | grep -v grep| grep rust_test | awk '{print $2}'| xargs ps -M -p
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
nkippis 27995 s021 0.0 S 31T 0:00.02 0:00.05 target/debug/rust_test
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
27995 0.0 S 31T 0:00.00 0:00.00
线程的运行顺序
线程的运行顺序是随机的,主线程不会等待子线程结束
use std::thread;
use std::time::Duration;
fn main() {
// 创建子线程
thread::spawn(|| {
for i in 1..=10 {
println!("a: {}", i);
thread::sleep(Duration::from_millis(1));
}
});
// 主线程执行程序
for i in 1..=5 {
println!(">>> b: {}", i);
thread::sleep(Duration::from_millis(1));
}
}
>>> b: 1
a: 1
>>> b: 2
a: 2
>>> b: 3
a: 3
>>> b: 4
a: 4
>>> b: 5
a: 5
等待线程完成join
创建线程后,主线程会继续执行,不会等待线程完成,因此主线程会先输出5行,然后输出10行。
使用join方法,等待线程完成,thread::spawn返回一个JoinHandle
join在结尾
use std::thread;
use std::time::Duration;
fn main() {
// 接收JoinHandle
let handle = thread::spawn(|| {
for i in 1..=10 {
println!("a: {}", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..=5 {
println!(">>> b: {}", i);
thread::sleep(Duration::from_millis(1));
}
// 等待线程完成之后,主线程才会结束
handle.join().unwrap();
}
>>> b: 1
a: 1
>>> b: 2
a: 2
>>> b: 3
a: 3
>>> b: 4
a: 4
>>> b: 5
a: 5
a: 6
a: 7
a: 8
a: 9
a: 10
join在中间
use std::thread;
use std::time::Duration;
fn main() {
// 接收JoinHandle
let handle = thread::spawn(|| {
for i in 1..=10 {
println!("a: {}", i);
thread::sleep(Duration::from_millis(1));
}
});
// 等待线程完成之后,主线程才继续执行
handle.join().unwrap();
for i in 1..=5 {
println!(">>> b: {}", i);
thread::sleep(Duration::from_millis(1));
}
}
a: 1
a: 2
a: 3
a: 4
a: 5
a: 6
a: 7
a: 8
a: 9
a: 10
>>> b: 1
>>> b: 2
>>> b: 3
>>> b: 4
>>> b: 5
在线程中使用move
使用move,闭包会接管它从环境中使用值的所有权,从而将这些值的所有权从一个线程移动到另一个线程
- move
- 因为闭包用了v,所以v被move进线程闭包
- 如果变量没被闭包用到,则不会move。
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let c = vec![1, 2, 3];
let t = thread::spawn(
move || {
println!("{:?}", v);
}
);
// v所有权被获取
// println!("v: {:?}", v);
// c没有被线程获取,所以可以打印
println!("c: {:?}", c);
t.join().unwrap();
}
线程管理
main线程是程序的主线程,一旦结束,则程序随之结束,同时各个子线程也将被强行终止。
- B线程是一个死循环,不会自己结束。
- 你没有持有B线程的句柄(JoinHandle),也没join它。
- 主线程等待A线程,A线程一启动B线程就结束了,因此主线程join很快完事。
- 然后主线程sleep 10ms,期间B线程还在疯狂打印。
- main最后返回,程序整体退出,B线程也马上“被杀”,你会看到打印停止。
use std::thread;
use std::time::Duration;
fn main() {
// 创建一个线程A
let new_thread = thread::spawn(move || {
// 再创建一个线程B
thread::spawn(move || {
loop {
println!("I am a new thread.");
thread::sleep(Duration::from_millis(1));
}
})
});
// 等待新创建的线程执行完成
new_thread.join().unwrap();
println!("Child thread is finish!");
// 睡眠一段时间,看子线程创建的子线程是否还在运行
thread::sleep(Duration::from_millis(10));
}
Child thread is finish!
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
I am a new thread.
线程间的通信
线程间通信,使用channel
use std::sync::mpsc::channel;中
mpsc:channel()返回一个元组(一个是发送端,一个是接收端)
- 接收端
.recv()方法,会阻塞当前线程,直到收到值.try_recv()方法,不会阻塞,而是以及返回一个<Result<T, E>
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
// 发送值
tx.send(val).unwrap();
});
// 主线程接收值
let s = rx.recv().unwrap();
println!("Got: {}", s);
}
传递多个值
rx.recv()只会获取一个值,而rx.iter()会循环接收所有消息,直到通道关闭(即所有发送端 tx 和 tx1 被 drop)
tx(send)实现了Clone,不同send共用内部状态(计数&缓冲区)。其内部已经做好了多线程所有权转移的同步和引用管理(比如原子引用计数等)。
所以,只要你是发消息的一端,直接tx.clone()发送端对象,不用再额外Arc包起来!
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let s1 = vec![
String::from("hello1"),
String::from("hello2"),
String::from("hello3"),
String::from("hello4")
];
for val in s1 {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let s1 = vec![
String::from("h1"),
String::from("h2"),
String::from("h3"),
String::from("h4")
];
for val in s1 {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for i in rx.iter() {
println!("Got: {}", i);
}
}
send问题
同一个通道
tx上尝试发送两种不同的类型 (String 和 usize),而Rust的 mpsc::channel() 是强类型的,默认情况下,一个通道只能发送同一种类型的数据。
use std::{sync::mpsc, thread};
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let s = String::from("Hello world");
// String类型
tx.send(s.clone()).unwrap();
// usize类型(报错)
tx.send(s.len()).unwrap();
});
let s = rx.recv().unwrap();
let n = rx.recv().unwrap();
println!("Got: {}, {}", s, n);
}
共享数据
让多个线程访问相同的共享数据
互斥锁
确保多个线程对同一数据进行访问时,只有一个线程可以访问数据,其他线程必须等待。
Rc和Arc都是Rust中的引用计数智能指针,用于在多个地方共享数据的所有权。它们的主要区别在于Arc是线程安全性,而Rc不是。
use std::{sync::{Mutex, Arc}, thread};
fn main() {
// 使用Arc包裹Mutex
let m = Arc::new(Mutex::new(0));
let mut v = vec![];
for _ in 0..10 {
// 使用Arc的clone
let value = Arc::clone(&m);
let t = thread::spawn(move || {
// 获取锁
let mut num = value.lock().unwrap();
*num += 1;
});
v.push(t);
}
for t in v {
t.join().unwrap();
}
println!("m = {:?}", m);
}
Arc错误使用
不能直接使用
Arc::new(&s),因为Arc<T>要求T是一个拥有所有权的类型
use std::{sync::{Mutex, Arc}, thread};
fn main() {
let s = String::from("hello");
// 报错
let a = Arc::new(&s);
let a2 = Arc::clone(&a);
let t1 = thread::spawn(move || {
a2.len()
});
let len = t1.join().unwrap();
println!("{} {}", a, len);
}
Sync与Send
- Send 是关于类型在线程间的所有权转移。
- Sync 是关于类型的不可变引用能否在线程间共享。
| 特性 | Send | Sync |
|---|---|---|
| 作用 | 表明类型实例能安全地从一个线程转移到另一个线程。 | 表明类型实例能安全地在多个线程间共享(通过不可变引用&T)。 |
| 数学定义 | T: Send意味着T可以安全地跨线程边界移动。 |
T: Sync等价于&T: Send,也就是T的引用可以安全地跨线程传递。 |
| 实现方式 | 大多数类型会自动实现Send,但包含原始指针(如*mut T)等类型除外。 |
若T是Send,那么&T必须实现Sync,除非T包含内部可变结构(如 Cell、RefCell)。 |
典型案例
- String 实现了 Send + Sync
- 因为 String 的所有权能够在线程间转移,而且不可变引用 &String 可以被多个线程同时访问。
- Rc
未实现 Send/Sync - Rc
采用非线程安全的引用计数,在线程间共享会引发数据竞争。 - Mutex
实现了 Send + Sync - 当 T: Send 时,Mutex
可在线程间转移,其内部锁机制也允许 &Mutex 被安全共享。
RefCell实现了 Sync 的条件 - 只有当 T: Sync 时,RefCell
才实现 Sync。但由于 RefCell 在运行时进行借用检查,所以多线程环境下使用 Mutex 更为合适。
- 当 T: Send 时,Mutex
作用域线程thread::scope
作用域线程允许在某个作用域内创建线程,并等待所有线程完成工作。
无需手动JoinHandle等待。
use std::thread;
fn main() {
thread::scope(|s| {})
}

浙公网安备 33010602011771号