thread in Rust

| 分类 Rust  | 标签 Rust 

thread::spawn

本文介绍Rust中的多线程并发。Rust提供了std::thread::spawn来创建线程。

use std::thread ;
use std::time::Duration;

fn main()
{
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from main thread!", i);
        thread::sleep(Duration::from_millis(1));

    }

    handle.join().unwrap();
}

其中thread::spawn的定义如下:

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where
    F: FnOnce() -> T,
    F: Send + 'static,
    T: Send + 'static, 

其返回值是JoinHandle类型。如果操作系统不能创建出来线程,respawn调用会panic。如果你的主程序,需要考虑spawn无法创建出来线程的情况,不能任由程序panic,那么你应该选择本文第三部分介绍的Builder::spawn,该接口会返回Result类型。

输出如下:

manu-Inspiron-5748 RBE/thread ‹master*› » cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/thread`
hi number 1 from main thread!
hi number 1 from spawned thread!
hi number 2 from main thread!
hi number 2 from spawned thread!
hi number 3 from main thread!
hi number 3 from spawned thread!
hi number 4 from main thread!
hi number 4 from spawned thread!
hi number 5 from spawned thread!
hi number 6 from spawned thread!
hi number 7 from spawned thread!
hi number 8 from spawned thread!
hi number 9 from spawned thread!

我们使用了join来等待线程退出,由于主进程和线程都有sleep 1毫秒的行为,如果没有主进程调用的join,那么随着主进程的退出,线程也退出了,后面的语句也不会打印出来。

use std::thread ;
use std::time::Duration;

fn main()
{
    let _handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from main thread!", i);
        thread::sleep(Duration::from_millis(1));

    }

    //handle.join().unwrap();
}

运行结果如下:

hi number 1 from main thread!
hi number 1 from spawned thread!
hi number 2 from main thread!
hi number 2 from spawned thread!
hi number 3 from main thread!
hi number 3 from spawned thread!
hi number 4 from spawned thread!
hi number 4 from main thread!
hi number 5 from spawned thread!

join: 等待线程退出

std::thread::spawn 函数的返回类型是JoinHandle, 我们可以在JoinHandle上调用join方法,等待线程退出。

use std::thread ;
use std::time::Duration;

fn main()
{
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    handle.join().unwrap();

    for i in 1..5 {
        println!("hi number {} from main thread!", i);
        thread::sleep(Duration::from_millis(1));

    }

}

上述代码中,主进程会等待线程退出之后,然后打印主进程的信息:

hi number 1 from spawned thread!
hi number 2 from spawned thread!
hi number 3 from spawned thread!
hi number 4 from spawned thread!
hi number 5 from spawned thread!
hi number 6 from spawned thread!
hi number 7 from spawned thread!
hi number 8 from spawned thread!
hi number 9 from spawned thread!
hi number 1 from main thread!
hi number 2 from main thread!
hi number 3 from main thread!
hi number 4 from main thread!

JoinHandle的join函数,原型如下:

pub fn join(self) -> Result<T>

其返回类型是Result类型。 因此在上面的示例中,我们需要调用unwrap或者expect来处理返回值,如果去除掉unwrap(),就会返回如下的警告:

warning: unused `std::result::Result` that must be used
  --> src/main.rs:14:5
   |
14 |     handle.join();
   |     ^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

建议主线程总是调用join来检测线程的运行状态,原因如下:

  • 如果主线程不等待线程退出,获取线程的返回值,主线程可能较早退出,导致线程也不得不退出,功能失控
  • 如果某个线程的工作崩溃,而无论主线程还是其他线程都不做任何处理,程序可能会失控。而主进程通过join,可以获得线程的返回值是Err,从而得知线程崩溃,作出正确的处理决断。

定制线程

我们通过std::thread::spawn 可以创建一个线程,而这种线程,一般是典型的线程,线程没有名字,而且线程栈大小为默认的2MB,如果像定制特殊的线程,则需要std::thread::Builder。

目前来讲,有两个参数可以设定:

  • name:设定线程的名字
  • stack_size: 制定线程的栈大小
        let builder = Builder::new().name(thread_name).stack_size(size);
        let child = builder.spawn(move || {
            println!("I am child {}", id);
            thread::sleep(Duration::from_secs(100));
            println!("I am child {}", current().name().unwrap());
        }).unwrap();

注意Builder::new之后,还是需要调spawn函数,来唤起线程。

pub fn spawn<F, T>(self, f: F) -> Result<JoinHandle<T>>

主进程为了调用join,需要对Builder::spawn的返回值做unwrap处理,方能调用join函数,否则:

error[E0599]: no method named `join` found for enum `std::result::Result<std::thread::JoinHandle<()>, std::io::Error>` in the current scope
  --> src/main.rs:21:15
   |
21 |         child.join().unwrap();
   |               ^^^^ method not found in `std::result::Result<std::thread::JoinHandle<()>, std::io::Error>`

整体示例代码如下:

use std::thread;
use std::thread::{Builder, current} ;
use std::time::Duration;

fn main()
{
    let mut v = vec![];
    for id in 0..5 {
        let thread_name = format!("thread-{}", id);
        let size: usize = 8192 ;
        let builder = Builder::new().name(thread_name).stack_size(size);
        let child = builder.spawn(move || {
            println!("I am child {}", id);
            thread::sleep(Duration::from_secs(100));
            println!("I am child {}", current().name().unwrap());
        }).unwrap();

        v.push(child)
    }
    for child in v{
        child.join().unwrap();
    }

    for i in 1..5 {
        println!("hi number {} from main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

}

输出如下:

I am child 0
I am child 1
I am child 2
I am child 3
I am child 4
I am child thread-1
I am child thread-3
I am child thread-0
I am child thread-2
I am child thread-4
hi number 1 from main thread!
hi number 2 from main thread!
hi number 3 from main thread!
hi number 4 from main thread!

我们通过top -H -p [PID]来查看线程的名字是否生效:

使用move闭包

一般来讲,move闭包总是伴随着thread::spawn。我们先看下面的示例:

use std::thread;

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

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

    handle.join().unwrap();
}

上述代码执行会出现报错:

error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
 --> src/main.rs:6:32
  |
6 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `v`
7 |         println!("Here's a vector: {:?}", v);
  |                                           - `v` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:6:18
  |
6 |       let handle = thread::spawn(|| {
  |  __________________^
7 | |         println!("Here's a vector: {:?}", v);
8 | |     });
  | |______^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
  |
6 |     let handle = thread::spawn(move || {
  |                                ^^^^^^^

error: aborting due to previous error

线程只是要打印v的内容,闭包会尝试borrow v,但是有个致命的问题,即Rust推断不出来,线程要运行多久,以及v这个变量引用是否一直有效。考虑如下场景:

use std::thread;

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

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

    drop(v); // oh no!

    handle.join().unwrap();
}

如果Rust允许执行上述代码,新创建的线程,很可能在主线程drop之后,调用println!,这种情况下,v已经失效了。这就不妙了。

通过在闭包之前,添加move关键字,我们会强制闭包获得他需要的值的所有权,而不仅仅是机遇Rust推导来获得值的借用。

修改正确之后的代码如下:

use std::thread;

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

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

    handle.join().unwrap();
}

上一篇     下一篇