我的RUST学习—— 【第十六章 16-1】使用线程同时运行代码

进程与线程

学过操作系统,或背过“八股文”的同学应该对进程线程的概念不陌生:

  • 一个操作系统下运行多个进程(process),一个进程下又运行多个线程(thread)。

将程序的计算拆成多个部分可以使用多核CPU的优势、改善性能。但是安全性很值得担忧

  • 竞争状态(Race conditions),多个线程以不同的顺序访问资源
  • 死锁(Deadlocks)
  • 只会发生在特定情况且难以稳定重现和修复的 bug

线程的创建

不同的编程语言有不同的方法实现线程。很多操作系统提供了创建新线程的API。

如果编程语言调用操作系统的API来创建线程,这种叫 1:1模型,即一个OS线程对应一个语言线程。

还有很多语言提供了自己的特殊实现。编程语言自己提供的线程称为 绿色线程,这种语言会在不同数量的 OS 线程上下文中执行它们。因此,这种模型被称为 M:N 模型:M 个绿色线程对应 N 个 OS 线程。

什么是运行时?

此处的运行时(Runtime)是指,编程语言会为二进制文件中的代码提供一个运行环境。比如,你想打篮球,但是没有场地,简单的话你可以拿一个脸盆当做球框;如果复杂一点,你可以叫一个施工队给你盖一个篮球场。这里的脸盆和篮球场就是所谓的 runtime。

那么很明显:

  • 运行时越复杂、庞大,提供的功能也就越多。
  • 运行时越简单,成本越低。

虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必须能够调用 C 语言,这点也是不能妥协的。

因此,Rust 标准库只提供了 1:1 线程模型实现。由于 Rust 是较为底层的语言,如果你愿意牺牲性能来换取抽象,以获得对线程运行更精细的控制及更低的上下文切换成本,你可以使用实现了 M:N 线程模型的 crate。

使用spawn创建新线程

调用 thread::spawn 函数并传递一个闭包,闭包中是线程代码。

use std::thread;
use std::time::Duration;
fn main () {
    thread::spawn(|| {
        for i in 1..10 {
            println!("sub thread: {}", i);
            thread::sleep(Duration::from_millis(10));
        }
    });
    for i in 1..5 {
        println!("main thread: {}", i);
        thread::sleep(Duration::from_millis(10));
    }
}

要注意的是,当主线程结束时,其他线程也会结束。

使用join等待所有线程结束。

主线程结束,其他线程会立刻结束。

可以通过将 thread::spawn 的返回值储存在变量中。返回值的类型是 JoinHandle,这是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。

use std::thread;
use std::time::Duration;
fn main () {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("sub thread: {}", i);
            thread::sleep(Duration::from_millis(10));
        }
    });
    for i in 1..5 {
        println!("main thread: {}", i);
        thread::sleep(Duration::from_millis(10));
    }
    handle.join();
}

线程与move闭包

move 闭包,我们曾在第十三章简要的提到过,其经常与 thread::spawn 一起使用,因为它允许我们在一个线程中使用另一个线程的数据。

可以在参数列表前使用 move 关键字强制闭包获取其使用的环境值的所有权。这个技巧在创建新线程将值的所有权从一个线程移动到另一个线程时最为实用。不过,移入子线程的变量将在主线程中不能使用。

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();
}

如果不在闭包前使用 move 关键字,编译器会报错。Rust会推断如何捕获v,由于闭包中只使用了v的引用,闭包尝试借用v。但是线程不知道要执行多久,那么v的声明周期可能在线程结束之前就结束了,因此会报错。

这也说明了所有权规则在帮助我们尽可能实现并发安全。

现在我们对线程和线程 API 有了基本的了解,让我们讨论一下使用线程实际可以 做 什么吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值