线程

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包含内部可变结构(如 CellRefCell)。

典型案例

  1. String 实现了 Send + Sync
    • 因为 String 的所有权能够在线程间转移,而且不可变引用 &String 可以被多个线程同时访问。
  2. Rc 未实现 Send/Sync
  3. Rc 采用非线程安全的引用计数,在线程间共享会引发数据竞争。
  4. Mutex 实现了 Send + Sync
    • 当 T: Send 时,Mutex 可在线程间转移,其内部锁机制也允许 &Mutex 被安全共享。
      RefCell 实现了 Sync 的条件
    • 只有当 T: Sync 时,RefCell 才实现 Sync。但由于 RefCell 在运行时进行借用检查,所以多线程环境下使用 Mutex 更为合适。

作用域线程thread::scope

作用域线程允许在某个作用域内创建线程,并等待所有线程完成工作。
无需手动JoinHandle等待。

use std::thread;

fn main() {
    thread::scope(|s| {})
}
posted @ 2026-08-10 10:00  lxd670  阅读(6)  评论(0)    收藏  举报