Rust 线程

线程是进程中最小的可执行单元。

线程允许我们将程序中的计算分成多个线程。同时运行多个任务可以提高代码的性能。然而,它也会增加复杂性。


在 Rust 中创建新线程

在 Rust 中,我们可以使用std模块中的thread::spawn()函数创建一个本地操作系统线程。spawn方法接受一个闭包作为参数。

这是thread::spawn()的语法,

thread::spawn(|| {
    // code to execute in the thread 
})

现在,让我们看一个示例。

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

fn main() {
    // create a thread
    thread::spawn(|| {
        // everything in here runs in a separate thread
        for i in 0..10 {
            println!("{} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(2));
        }
    });

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

输出

0 from the main thread!
0 from the spawned thread!
1 from the main thread!
1 from the spawned thread!
2 from the main thread!
3 from the main thread!
2 from the spawned thread!
4 from the main thread!

在上面的示例中,我们使用thread::spawn()函数创建了一个线程。该线程循环遍历0..5并打印当前值。

同样,我们有一个主线程,其中循环遍历0..5并打印当前值。

我们还调用thread::sleep来强制线程暂停执行一小段时间,以便另一个线程运行。

请注意,我们在创建的线程中休眠2毫秒,在主线程中休眠1毫秒。

每次运行此程序的输出可能会略有不同。这里要记住的重要一点是,如果主线程完成,所有其他线程都会被关闭,无论它们是否已完成运行。

因此,即使创建的线程应该一直打印到i为9,但由于主线程关闭,它只运行到2


Rust 中的 Join Handles

创建的线程始终返回一个 join handle。如果我们希望创建的线程完成执行,我们可以将thread::spawn的返回值保存在一个变量中,然后在其上调用join()方法。

thread::spawn的返回类型JoinHandle上的join()方法会等待创建的线程完成。

让我们看一个例子。

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

fn main() {
    // create a thread and save the handle to a variable
    let handle = thread::spawn(|| {
        // everything in here runs in a separate thread
        for i in 0..10 {
            println!("{} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(2));
        }
    });

    // main thread
    for i in 0..5 {
        println!("{} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
    
// wait for the separate thread to complete handle.join().unwrap();
}

输出

0 from the main thread!
0 from the spawned thread!
1 from the main thread!
2 from the main thread!
1 from the spawned thread!
3 from the main thread!
2 from the spawned thread!
4 from the main thread!
3 from the spawned thread!
4 from the spawned thread!
5 from the spawned thread!
6 from the spawned thread!
7 from the spawned thread!
8 from the spawned thread!
9 from the spawned thread!

在这里,我们保存了thread::spawn()函数的返回值,并将其绑定到名为handle的变量。

在代码的最后一行,我们调用handlejoin()方法。调用handle上的join()会阻塞线程,直到线程终止。

两个线程(主线程和创建的线程)会持续一段时间交替运行,但主线程由于handle.join()而等待,直到创建的线程完成才结束。

如果我们把handle.join()移到最后一个循环之前,输出将会改变,打印语句也不会交错。

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

fn main() {
    // create a thread and save the handle to a variable
    let handle = thread::spawn(|| {
        // everything in here runs in a separate thread
        for i in 0..10 {
            println!("{} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(2));
        }
    });
    
// wait for the separate thread to complete handle.join().unwrap();
// main thread for i in 0..5 { println!("{} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } }

输出

0 from the spawned thread!
1 from the spawned thread!
2 from the spawned thread!
3 from the spawned thread!
4 from the spawned thread!
5 from the spawned thread!
6 from the spawned thread!
7 from the spawned thread!
8 from the spawned thread!
9 from the spawned thread!
0 from the main thread!
1 from the main thread!
2 from the main thread!
3 from the main thread!
4 from the main thread!

因此,知道join()在哪里被调用很重要。这将决定线程是否同时运行。


在 Rust 中使用 move 闭包与线程

可以通过将一个值作为参数传递给thread::spawn()函数来将其移动到单独的线程中。

让我们看一个例子。

use std::thread;

fn main() {
    // main thread starts here
    let message = String::from("Hello, World!");

// move the message value to a separate thread let handle = thread::spawn(move || { println!("{}", message); });
// wait for the thread to finish handle.join().unwrap(); }

输出

Hello, World!

在这里,传递给thread::spawn()函数的闭包||使用move关键字来指示它获取message变量的所有权。

当一个值被移动到线程中时,该值的所有权就转移给了该线程,主线程将无法再访问该值。

这意味着即使主线程完成,闭包也可以使用message变量。

让我们看看如果我们不在闭包前面使用move关键字会发生什么。

use std::thread;

fn main() {
    let message = String::from("Hello, World!");

// using the message variable without a move let handle = thread::spawn(|| { println!("{}", message); });
handle.join().unwrap(); }

输出

error[E0373]: closure may outlive the current function, but it borrows `message`, which is owned by the current function
 --> src/main.rs:7:32
  |
7 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `message`
8 |         println!("{}", message);
  |                        ------- `message` is borrowed here
  |

在这种情况下,程序将无法编译。在这里,Rust 会尝试将message变量借用给单独的线程。

7 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `message`

然而,Rust 不知道创建的线程会运行多久。因此,它无法判断对message变量的引用是否始终有效。

通过在闭包前面添加move关键字,我们强制闭包获取message变量或闭包内使用的任何变量的所有权。

我们正在告诉 Rust 主线程不再使用message变量。这是 Rust 所有权的一个典型示例,它如何使我们免于发生意外。要了解更多关于 Rust 所有权的信息,请访问Rust 所有权

请注意,将值移动到线程中对于并行处理可能很有用,但如果使用不当,也可能成为错误的来源。


在 Rust 中通过线程发送消息

在 Rust 中,线程可以通过通道发送消息来相互通信。通道是一种在线程之间发送值的方式,它可以用于同步通信和避免数据竞争。

我们使用std::sync::mspsc模块中的channel()函数在 Rust 中创建一个通道。

让我们看看如何使用通道在线程之间进行通信。

use std::sync::mpsc;
use std::thread;

fn main() {
    // main thread starts here
    // create a new channel
    let (sender, receiver) = mpsc::channel();

    // spawn a new thread
    let handle = thread::spawn(move || {
        // receive message from channel
        let message = receiver.recv().unwrap();

        println!("Received message: {}", message);
    });

    let message = String::from("Hello, World!");
    // send message to channel
    sender.send(message).unwrap();

    // wait for spawned thread to finish
    handle.join().unwrap();
}

输出

Received message: Hello, World!

在这里,我们使用channel()函数创建一个通道。std::sync::mpsc模块提供了多生产者、单消费者(mspc)通道,可用于在线程之间发送值。

// create a new channel
let (sender, receiver) = mpsc::channel();

senderreceiver变量代表通道的两个端点。发送者端点用于发送消息,而接收者端点用于接收消息。

// spawn a new thread
let handle = thread::spawn(move || {
    // receive message from channel
    let message = receiver.recv().unwrap();

    println!("Received message: {}", message);
});

我们还使用thread::spawn()函数创建一个创建的线程。传递给函数的闭包使用receiver.recv()方法接收消息。

recv()方法会阻塞,直到通道上收到消息,它会返回一个Result,指示是否收到了消息或发生了错误。

let message = String::from("Hello, World!");
// send message to channel
sender.send(message).unwrap();

在主线程中,创建了一个message并通过sender.send()方法发送。send()方法返回一个Result,指示消息是否成功发送或发生了错误。

// wait for spawned thread to finish
handle.join().unwrap();

最后,在 handle 上调用join()方法,以等待创建的线程在程序退出前完成。

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战