-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
panhy
committed
Apr 23, 2024
1 parent
7389b0a
commit 68ae402
Showing
5 changed files
with
72 additions
and
2 deletions.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
docs/20_simple_concurrency/01_a_simple_concurrency_example.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# Rust简易并发实例 | ||
|
||
## 一、概述 | ||
|
||
- **并发(concurrency)**:并发是多任务同时运行,但是并不是同时运行,而是交替运行。 | ||
|
||
- **并行(parallelism)**:并行是多任务同时运行。 | ||
|
||
在一些情况下,人们会将并发这个词用来代表并发和并行。尤其是在日常语言中或者一般性的讨论中,人们可能会使用并发来表示多个任务同时执行的概念,而不特别区分并发和并行之间的细微差别。因此在这一节中,我们用并发代表`concurrency`和`parallelism`。 | ||
|
||
在 Rust 中,实现高并发主要通过异步编程模型来处理。本节将介绍为什么需要并发以及 Rust 异步编程的基本概念。 | ||
|
||
## 二、为什么需要并发? | ||
|
||
程序的并发的主要目的是提高系统的性能、资源利用率和响应能力。我们首先需要了解程序处理任务的两种类型: | ||
|
||
- CPU密集型:CPU 密集型任务指的是占用 CPU 资源的任务,例如:文件压缩、加密、解密等; | ||
- IO密集型:IO 密集型任务指的是占用 IO 资源的任务,例如:网络传输、文件读取、数据库操作等。 | ||
|
||
针对不同类型的任务(CPU 密集型和 IO 密集型),需要不同的并发方案来达到最佳性能。对于CPU密集型任务,我们就通常可以利用多CPU或者多核进行处理;针对IO密集型任务,我们通常可以利用多线程进行处理。 | ||
|
||
## 三、Rust 异步编程的概念 | ||
|
||
### 3.1 异步与多线程 | ||
|
||
我们先来了解以下一些概念: | ||
|
||
- **同步(synchronous)**:同步是指程序执行到某个任务时,必须等待该任务执行完毕才能继续执行下一个任务。 | ||
- **异步(asynchronous)**:异步是指程序执行到某个任务时,不需要等待该任务执行完毕,程序可以继续执行下一个任务。 | ||
- **多线程(multithreading)**:多线程是指在单个应用程序中同时执行多个线程,每个线程都可以执行不同的任务。 | ||
|
||
在 Rust 中,实现并发有多种方式,其中异步编程是一种常用的方式之一。多线程是一种实现并发的另一种方式。 | ||
|
||
### 3.2 并发的实现方案 | ||
|
||
#### 3.3.1 多线程的局限性 | ||
|
||
我们以Web服务器作为示例,Web服务器可以使用多线程来处理多个请求。针对每个请求都开启一个原生的系统线程,虽然了提高性能,却引入了新的复杂性,例如: | ||
|
||
- **执行顺序无法预测**: 例如,原本的请求应该顺序应该是 get、post、put、delete,但是由于多线程,顺序可能不是这样的,可能 delete 先执行完成,导致其他请求处理失败; | ||
- **死锁**: 两个线程彼此等待对方使用完持有的资源,导致线程无法继续; | ||
- **竞态条件(race condition)**: 多个线程同时访问共享资源,导致结果不可预期,例如:多个线程同时修改同一个变量,导致结果不可预期。 | ||
|
||
其实多线程还存在另外一个问题,在了解这个问题之前,我们先了解一下线程的两种模型: | ||
|
||
- **1:1 模型**:每个线程都对应一个原生的系统线程。 | ||
- **M:N 模型**: M线程对应 N 个原生的系统线程。这里的 M 线程叫做准线程,或者叫做绿色线程,都指的是语言线程,N 个原生的系统线程叫做操作系统线程。 | ||
|
||
rust标准库中就是实现了 1:1 模型。一般来说,操作系统对准线程数是有限制的,它受到栈内存和虚拟内存的影响,而且线程切换的时候还有上下文切换的成本和管理线程的其他资源成本。所以多线程并不是万能的,它不适合所有的需求场景,尤其是在Web服务器中的高并发场景下。 | ||
|
||
#### 1.3.2 Nginx 的事件驱动并发模型 | ||
|
||
Nginx 并不是典型的多线程服务器,Nginx 采用了事件驱动的并发模型,使用单线程或少量线程处理并发连接,利用非阻塞 I/O 和事件驱动的方式来提高性能和可伸缩性。下面是一些关键的概念: | ||
|
||
- **事件驱动**:Nginx 使用事件驱动的方式来处理并发连接,通过监听事件并在事件发生时触发相应的处理函数来实现高效的并发处理。这种模型避免了传统多线程模型中线程切换的开销,提高了服务器的性能和响应速度。 | ||
|
||
- **单线程或少量线程**:Nginx 通常使用单线程或少量线程来处理并发连接,每个连接都在一个独立的事件处理函数中处理,避免了线程间的竞争和同步开销,提高了系统的效率和可靠性。 | ||
|
||
- **非阻塞 I/O**:Nginx 使用非阻塞 I/O 来处理网络请求,当一个 I/O 操作无法立即完成时,Nginx 不会阻塞当前线程,而是继续处理其他请求,等待事件就绪后再进行处理。这种方式避免了线程在等待 I/O 操作完成时的闲置,提高了系统的吞吐量和性能。 | ||
|
||
#### 1.3.3 Rust 异步编程模型 | ||
|
||
经过上文的介绍,可以知道多线程并不适合任意的场景,特别是有高并发需求的web服务器。在rust中,可以使用如下图的方式来实现高并发(图来自互联网): | ||
|
||
![rust异步编程](img/image.png) | ||
|
||
每个 HTTP 请求被异步 Web Server 接收,Web Server 会将请求分发给不同的异步任务,由异步运行时任务安排各个异步任务在可用的 CPU 上执行,最后返回结果。这就是rust的**异步编程模型**。 | ||
|
||
Rust 的异步编程模型基于 Future 和 Executor,通过 async/await 关键字和 futures 库来实现。异步编程允许在单个线程或者多个线程上处理多个并发任务,提高系统的性能和资源利用率。 | ||
|
||
## 三、编写 Rust 异步程序 |
Empty file.
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters