Skip to content

Commit

Permalink
咕咕咕
Browse files Browse the repository at this point in the history
  • Loading branch information
chuigda committed Jul 12, 2023
1 parent 9766e98 commit 1518ae0
Showing 1 changed file with 187 additions and 0 deletions.
187 changes: 187 additions & 0 deletions markdown/asynkronous-and-koroutine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
!!meta-define:ident:asynkronous-and-koroutine.md
!!meta-define:title:异步与协程
!!meta-define:author:Chuigda WhiteGive
!!meta-define:time:2023-07-12T22:53:43+08:00
!!meta-define:tags:异步,协程,程序设计语言理论
!!meta-define:brief:本文简单地介绍了“异步”与协程的理念,以及它们在程序设计语言中的应用。

在 3202 年,异步和协程已经成为了编程语言中人均必备的装逼手段。
然而一些程序员对于异步和协程一类的理念还是一知半解,本文将会尝试以尽可能简单的方式介绍它们。
受笔者能力所限,本文的介绍非常不形式化,并且往往会在一些需要抽象的时候反而陷入实现相关的具体细节。
不足之处,敬请谅解。

本文中所有代码均为伪代码。

## “同步” API 与阻塞

假设我们有一段代码,它首先从远程计算机获取数据,然后执行一组计算:

```javascript
data = readData(connection)
processData(data)
```

这段代码里涉及了两种任务:
- 从远程计算机获取数据的 `readData` 是一个 *IO 任务*,它并不消耗任何计算资源,只是在等待远程计算机的响应就绪。
- 对数据进行处理的 `processData` 是一个*计算任务*,它消耗本地计算机的 CPU 资源。

在上述代码中,当程序执行到 `readData` 时,如果远程计算机没有响应,那么程序就要停下来等待。对于一个“同步” 的
API 来说,它会让线程挂起,将控制权交还给操作系统,让操作系统调度另一个可以执行的线程。 直到远程计算机到来时,
操作系统将进程唤醒,程序才能继续执行。也就是说,同步的 API 在需要等待 IO 时,会**阻塞整个线程的执行**

那么,如果这段代码属于某个服务程序:

```javascript
while (true) {
connection = acceptConnection()

data = readData(connection)
processData(data)
}
```

那么,当一个客户端连接到这个服务程序时,如果这个客户端和服务器之间的网络连接很慢,或是客户端发送的数据很大,
那么这个服务程序就会卡在 `readData` 上,而无法处理其他客户端的请求。

## 多线程和多进程

在这种情况下,如果一个程序想要尽可能利用计算机上的 CPU 资源,也就是希望在等待 IO 的时候尽量有活可做,一种很容易想到的
方法就是启动多个线程或者多个进程。这样,当一个线程在等待 IO 的时候,其他线程还可以继续执行。例如,在上面的服务程序中,
我们可以为每个客户端连接启动一个线程:

```javascript
while (true) {
connection = acceptConnection()
launchThread(function () {
data = readData(connection)
processData(data)
})
}
```

或者,为每个客户端连接创建一个进程:

```javascript
while (true) {
connection = acceptConnection()
if (fork() == 0) {
data = readData(connection)
processData(data)
exit()
}
}
```

这样,当一个线程被 IO 阻塞时,其他线程还可以继续执行。

## 多线程的局限性

然而,多线程也有它的局限性:
- 创建和维护线程会消耗一定的系统资源
- 当一个线程阻塞时,必须要进入操作系统内核态,切换线程上下文,并且执行线程调度算法
- 操作系统的线程调度器很强大,但它很大程度上是为“通用”的场景设计的,它只能按照尽可能公平的方式进行调度
- 线程之间进行通信需要使用同步原语,这些东西也有开销,并且容易出错

## 非阻塞的 API

为了解决上述问题,我们可以使用非阻塞的 API。非阻塞的 API 会立即返回,而不是等待 IO 完成:

```javascript
data = readDataNonBlocking()
while (data == null) {
data = readDataNonBlocking()
}

processData(data)
```

上面的代码使用了一个非阻塞的 API `readDataNonBlocking`,当数据没有准备好时,它会立即返回 `null`
上面的代码使用轮询的方式来等待数据准备好。不过实际上,我们可以使用更高效的方式来等待数据的到来,
例如 Unix 操作系统会提供一个这样的 `select` API:

```javascript
connections = []

while (true) {
connection = acceptConnection()
connections.push(connection)

readyConnections = select(connections) // 从 connections 中选出已经准备好的连接
if (readyConnections.length > 0) {
for (connection in readyConnections) {
data = readData(connection) // 肯定不会阻塞
processData(data)
}
}
}
```

这样的代码就克服了线程的局限性:
- 一个线程可以同时处理多个 IO 任务,每个 IO 任务只占用 `connections` 数组中的一个元素
- 当一个 IO 任务不能推进时,线程可以处理其他 IO 任务
- 所有操作在一个线程之内完成,并且不需要同步原语

## 回调式 API

早期 JavaScript 使用回调式的 API,例如:

```javascript
readData(connection, function (data) {
processData(data)
})
```

它的工作方式是这样的:

```javascript
connections = []

function readData(connection, callback) {
connections.push({
op: readDataNonBlocking,
connection: connection,
callback: callback
})
}

// 可以认为下面的代码一直在运行,只要 CPU 有空闲,就会执行这些代码
whenCPUIdle(function () {
readyConnections = select(connections)
for (connection in readyConnections) {
op = connection.op
data = op(connection)
connection.callback(data)
}
})
```

回调式的 API 比直接写一个大 while 要好一丢丢,但如果嵌套的多了:

```javascript
readData1(connection, function (data) {
readData2(connection, function (data) {
readData3(connection, function (data) {
processData(data)
writeData(connection, function () {
// ...
})
})
})
})
```

就会陷入无尽的回调地狱。因此,我们需要一种更好的方式来组织代码。

## 协程

简单来说,协程就是另一种对 “big while” 的封装方式。有了协程之后,就可以像原先使用同步 API 一样,写出简洁的代码:

```javascript
data = await readDataAsync(connection)
processData(data)
```

与原先同步的 `readData``readDataAsync` 并不会阻塞线程,而只是将当前的协程暂停,控制权会被移交给程序内部的协程调度器,
而无须劳烦操作系统。协程具体的实现总体上和回调式的 API 的实现有异曲同工之妙。

*今天先写到这吧,累死我了, 下次再讲 stackful 和 stackless,multithread runtime 和 work stealing,咕咕咕。*

0 comments on commit 1518ae0

Please sign in to comment.