Rust学习笔记之并发(一)

简介: Rust学习笔记之并发(一)

光阴者,百代之过客,唯有奋力奔跑,方能生风其,是时势造英雄,英雄存在时代

大家好,我是柒八九

今天,我们继续Rust学习笔记的探索。我们来谈谈关于Rust学习笔记之并发的相关知识点。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. Rust学习笔记之Rust环境配置和入门指南
  2. Rust学习笔记之基础概念
  3. Rust学习笔记之所有权
  4. Rust学习笔记之结构体
  5. Rust学习笔记之枚举和匹配模式
  6. Rust学习笔记之包、Crate和模块
  7. Rust学习笔记之集合
  8. Rust学习笔记之错误处理
  9. Rust学习笔记之泛型、trait 与生命周期
  10. Rust学习笔记之闭包和迭代器
  11. Rust学习笔记之智能指针

你能所学到的知识点

  1. {并发编程|Concurrent Programming} VS {并行编程|Parallel programming}推荐阅读指数 ⭐️⭐️⭐️
  2. 使用线程同时运行代码 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
  3. 使用消息传递在线程间传送数据 推荐阅读指数 ⭐️⭐️⭐️⭐️
  4. 共享状态并发 推荐阅读指数 ⭐️⭐️⭐️
  5. 使用 Sync 和 Send trait 的可扩展并发 推荐阅读指数 ⭐️⭐️⭐️

好了,天不早了,干点正事哇。


{并发编程|Concurrent programming} VS {并行编程|Parallel Programming}

{并发编程|Concurrent programming}{并行编程|Parallel Programming}都是指在计算机程序中同时执行多个任务或操作的编程方式,但它们在实现方式和目标上存在一些异同点。

{并发编程|Concurrent programming}指的是在一个程序中同时进行多个任务,这些任务可以是独立的,相互之间没有直接的依赖关系

在并发编程中,这些任务通常是通过交替执行、时间片轮转或事件驱动的方式来实现并行执行的假象。

并发编程的目标是提高程序的效率、响应性和资源利用率

{并行编程|Parallel Programming}是指在硬件级别上同时执行多个任务,利用计算机系统中的多个处理单元(例如多核处理器)或多台计算机来同时处理多个任务

在并行编程中,任务之间可以有依赖关系,需要进行任务的分割和协调

并行编程的目标是实现更高的计算性能和吞吐量

总结一下,并发编程和并行编程的异同点如下:

  1. 目标:并发编程旨在提高程序的效率、响应性和资源利用率,而并行编程旨在实现更高的计算性能和吞吐量。
  2. 执行方式:并发编程通过交替执行、时间片轮转或事件驱动的方式,在一个程序中同时进行多个任务的执行;并行编程通过同时使用多个处理单元或计算机来同时执行多个任务。
  3. 任务关系:并发编程中的任务通常是独立的,相互之间没有直接的依赖关系;而并行编程中的任务可能存在依赖关系,需要进行任务的分割和协调。
  4. 实现方式:并发编程可以通过线程、进程、协程等机制来实现;并行编程可以通过并行算法、分布式计算等技术来实现。

{并发编程|Concurrent programming},代表程序的不同部分相互独立的执行,而 {并行编程|parallel programming}代表程序不同部分于同时执行,这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。


使用线程同时运行代码

在大部分现代操作系统中,已执行程序的代码在一个 {进程|Process}中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。运行这些独立部分的功能被称为 {线程|Threads}

进程和线程

{进程|Process}{线程|Threads}是操作系统中用于执行程序的基本执行单位,它们之间有着密切的关系,但又有一些本质的区别。

{进程|Process}是操作系统中的一个运行实例,它包含了程序执行所需的代码、数据和资源。

每个进程都有自己的地址空间,包括独立的堆、栈和全局数据区域。进程之间是相互独立的,它们不能直接访问其他进程的内部数据,通信和数据共享需要通过操作系统提供的机制(如管道、共享内存等)进行。

{线程|Threads}进程中的一个执行流,它是进程中的一个独立单元,负责执行程序中的指令

一个进程可以拥有多个线程,这些线程共享进程的地址空间和资源,包括堆、栈和全局数据区域。不同线程之间可以直接访问进程的内部数据,它们共享相同的上下文环境,因此线程之间的通信和数据共享比进程之间更加高效。

进程和线程之间的关系

  1. 进程是程序的执行实例,它可以包含多个线程。
  2. 进程之间是独立的,相互之间不能直接访问对方的内部数据,通信需要通过操作系统提供的机制。
  3. 同一进程内的多个线程共享进程的地址空间和资源,它们可以直接访问进程的内部数据。
  4. 进程之间的切换开销比线程之间的切换开销更大,因为进程切换需要保存和恢复整个进程的上下文环境,而线程切换只需要保存和恢复线程的上下文环境。
  5. 进程之间的并行执行是由操作系统的调度器决定的,而线程之间的并行执行是由线程调度器(也称为内核级线程调度器或用户级线程调度器)决定的。

线程的问题和类型

将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:

  • {竞争状态|Race conditions},多个线程以不一致的顺序访问数据或资源
  • {死锁|Deadlocks},两个线程相互等待对方停止使用其所拥有的资源,这会阻止它们继续运行
  • 只会发生在特定情况且难以稳定重现和修复的 bug

1:1线程

很多操作系统提供了创建新线程的 API。这种由编程语言调用操作系统 API 创建线程的模型有时被称为 1:1一个 OS 线程对应一个语言线程

java创建一个1:1线程

// 创建线程类
class MyThread extends Thread {
    public void run() {
        // 线程执行的代码
    }
}
// 创建并启动线程
public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

绿色线程

很多编程语言提供了自己特殊的线程实现。编程语言提供的线程被称为 {绿色|green}线程,使用绿色线程的语言会在不同数量的 OS 线程的上下文中执行它们。为此,绿色线程模式被称为 M:N 模型:M 个绿色线程对应 NOS 线程,这里 MN 不必相同。

javascript创建一个绿色线程

// 在 worker.js 文件中的代码
self.onmessage = function(event) {
  // 处理消息
  // 发送消息回主线程
  self.postMessage('处理完成');
};
// 在主线程中的代码
var worker = new Worker('worker.js');
worker.onmessage = function(event) {
  // 处理 worker 返回的消息
};
worker.postMessage('开始处理');

Web Worker 引入了一种不同的线程模型,它在 JavaScript 运行时环境中实现了类似线程的机制,但是并不依赖于操作系统的线程Web Worker底层使用了浏览器提供的异步事件模型,利用了浏览器的多线程特性

Web Worker 并非真正的操作系统级线程,它是在 JavaScript 运行时环境中模拟的线程。每个 Web Worker 都有自己的上下文和事件循环,它们之间通过消息传递进行通信。因此,Web Worker 通常被称为绿色线程,因为它们在用户级别实现了线程的效果,而不需要依赖操作系统的线程管理。


在当前上下文中,{运行时|Runtime} 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 没有运行时,一般意味着 小运行时。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。

绿色线程的 M:N 模型需要更大的语言运行时来管理这些线程。因此,Rust 标准库只提供了 1:1 线程模型实现


使用 spawn 创建新线程

为了创建一个新线程,需要调用 thread::spawn 函数并传递一个闭包,并在其中包含希望在新线程运行的代码。

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

当主线程结束时,新线程也会结束,而不管其是否执行完毕。这个程序的输出可能每次都略有不同。

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

thread::sleep 调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头,甚至即便我们告诉新建的线程打印直到 i 等于 9 ,它在主线程结束之前也只打印到了 5。


使用 join 等待所有线程结束

由于主线程结束,上面的代码大部分时候不光会提早结束新建线程,甚至不能实际保证新建线程会被执行。其原因在于无法保证线程运行的顺序!

可以通过将 thread::spawn返回值储存在变量中来修复新建线程部分没有执行或者完全没有执行的问题thread::spawn 的返回值类型是 JoinHandleJoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束

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

通过调用 handlejoin 会阻塞当前线程直到 handle 所代表的线程结束。{阻塞|Blocking}线程意味着阻止该线程执行工作或退出。

运行上面的代码应该会产生类似这样的输出:

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

这两个线程仍然会交替执行,不过主线程会由于 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();
}

通过告诉 Rustv 的所有权移动到新建线程,我们向 Rust 保证主线程不会再使用 vmove 关键字覆盖了 Rust 默认保守的借用,但它不允许我们违反所有权规则。

相关文章
|
1月前
|
Rust 安全 云计算
Rust语言入门:安全性与并发性的完美结合
【10月更文挑战第25天】Rust 是一种系统级编程语言,以其独特的安全性和并发性保障而著称。它提供了与 C 和 C++ 相当的性能,同时确保内存安全,避免了常见的安全问题。Rust 的所有权系统通过编译时检查保证内存安全,其零成本抽象设计使得抽象不会带来额外的性能开销。Rust 还提供了强大的并发编程工具,如线程、消息传递和原子操作,确保了数据竞争的编译时检测。这些特性使 Rust 成为编写高效、安全并发代码的理想选择。
28 0
|
3月前
|
Rust 安全 调度
30天拿下Rust之并发
30天拿下Rust之并发
37 0
|
4月前
|
数据采集 Rust 安全
Rust在网络爬虫中的应用与实践:探索内存安全与并发处理的奥秘
【8月更文挑战第31天】网络爬虫是自动化程序,用于从互联网抓取数据。随着互联网的发展,构建高效、安全的爬虫成为热点。Rust语言凭借内存安全和高性能特点,在此领域展现出巨大潜力。本文探讨Rust如何通过所有权、借用及生命周期机制保障内存安全;利用`async/await`模型和`tokio`运行时处理并发请求;借助WebAssembly技术处理动态内容;并使用`reqwest`和`js-sys`库解析CSS和JavaScript,确保代码的安全性和可维护性。未来,Rust将在网络爬虫领域扮演更重要角色。
89 1
|
4月前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
76 0
|
4月前
|
安全 开发者 数据安全/隐私保护
Xamarin 的安全性考虑与最佳实践:从数据加密到网络防护,全面解析构建安全移动应用的六大核心技术要点与实战代码示例
【8月更文挑战第31天】Xamarin 的安全性考虑与最佳实践对于构建安全可靠的跨平台移动应用至关重要。本文探讨了 Xamarin 开发中的关键安全因素,如数据加密、网络通信安全、权限管理等,并提供了 AES 加密算法的代码示例。
70 0
|
4月前
|
开发框架 Android开发 iOS开发
跨平台开发的双重奏:Xamarin在不同规模项目中的实战表现与成功故事解析
【8月更文挑战第31天】在移动应用开发领域,选择合适的开发框架至关重要。Xamarin作为一款基于.NET的跨平台解决方案,凭借其独特的代码共享和快速迭代能力,赢得了广泛青睐。本文通过两个案例对比展示Xamarin的优势:一是初创公司利用Xamarin.Forms快速开发出适用于Android和iOS的应用;二是大型企业借助Xamarin实现高性能的原生应用体验及稳定的后端支持。无论是资源有限的小型企业还是需求复杂的大公司,Xamarin均能提供高效灵活的解决方案,彰显其在跨平台开发领域的强大实力。
53 0
|
4月前
|
Rust 安全 数据处理
【揭秘异步编程】Rust带你走进并发设计的神秘世界——高效、安全的并发原来是这样实现的!
【8月更文挑战第31天】《异步编程的艺术:使用Rust进行并发设计》一文探讨了如何利用Rust的`async`/`await`机制实现高效并发。Rust凭借内存安全和高性能优势,成为构建现代系统的理想选择。文章通过具体代码示例介绍了异步函数基础、并发任务执行及异步I/O操作,展示了Rust在提升程序吞吐量和可维护性方面的强大能力。通过学习这些技术,开发者可以更好地利用Rust的并发特性,构建高性能、低延迟的应用程序。
51 0
|
7月前
|
Rust 并行计算 安全
Rust中的并行与并发优化:释放多核性能
Rust语言以其内存安全和高效的并发模型在并行计算领域脱颖而出。本文深入探讨了Rust中的并行与并发优化技术,包括使用多线程、异步编程、以及并行算法等。通过理解并应用这些技术,Rust开发者可以有效地利用多核处理器,提高程序的性能和响应能力。
|
Rust 安全 API
Rust学习笔记之并发(二)
Rust学习笔记之并发(二)
|
缓存 Rust 前端开发
GIAC-2022sh 学习笔记 | QEngine: 支付宝实时行情服务 Rust 跨端实践
GIAC-2022sh 学习笔记 | QEngine: 支付宝实时行情服务 Rust 跨端实践
218 0
GIAC-2022sh 学习笔记 | QEngine: 支付宝实时行情服务 Rust 跨端实践