Levar você a entender vários modelos de E/S

prefácio

Falando em E/S, acredito que os desenvolvedores front-end estejam mais familiarizados com as solicitações de rede. Porque a maioria dos outros navegadores de operação de E/S fazem isso por nós. Mas, como desenvolvedores de software, a E/S é algo em que não podemos evitar o foco. Hoje vamos dar uma olhada nos vários modelos de E/S que são comuns atualmente.

O que é E/S?

I/O vem do inglês input/output (input/output) , que se refere à entrada e saída de dados entre a memória (interna e externa) ou outros dispositivos periféricos. Normalmente, uma E/S completa em um processo do usuário é dividida em dois estágios: E/S no espaço do processo do usuário e no espaço do kernel, e E/S no espaço do kernel e no espaço do dispositivo (disco, rede, etc.). É dividido em três tipos: E/S de memória , E/S de rede e E/S de disco . Normalmente, E/S refere-se aos dois últimos.

Bloqueando E/S

O bloqueio de E/S é o modelo mais comumente usado e o mais amigável ao desenvolvedor. Porque neste modelo, a lógica de execução é linear e os desenvolvedores só precisam prestar atenção à lógica de execução do programa do início ao fim.

O padrão de comportamento interativo entre os threads do aplicativo e o kernel neste modelo é o seguinte:

Pode-se ver na figura acima que sob o modelo de bloqueio de E/S, o processo de chamar o sistema para ler os dados até o aplicativo obter os dados é linear, o que está de acordo com nossa sequência de pensamento normal. No entanto, esse modo apresenta uma grande desvantagem, ou seja, no processo de preparação de dados pelo sistema operacional, o thread do aplicativo fica bloqueado e ocioso o tempo todo.

Portanto, neste modelo, um thread só pode manipular a comunicação de dados em uma conexão de rede. Mesmo que não haja dados na conexão, a thread só pode bloquear na operação de leitura do Socket (para aguardar os dados do peer). Embora esse modelo seja ineficiente para o aplicativo como um todo, é o mais fácil para os desenvolvedores implementarem e usarem, portanto, todas as principais plataformas definem o Socket como bloqueador por padrão.

E/S sem bloqueio (E/S sem bloqueio)

O modelo de E/S sem bloqueio é diferente do modelo de E/S com bloqueio. Seu modo de comportamento de interação entre o thread do aplicativo e o kernel é o seguinte:

可以看到,与阻塞 I/O 模型正相反,在非阻塞 I/O 模型下,当用户空间线程向操作系统内核发起 I/O 请求 后,内核会执行这个 I/O 操作,如果这个时候数据尚未就绪,就会立即将“未就绪” 的状 态以错误码形式(比如:EAGAIN/EWOULDBLOCK) , 返回给这次 I/O 系统调用的发起 者。 而后者就会根据系统调用的返回状态来决定下一步该怎么做。

在非阻塞模型下,位于用户空间的 I/O 请求发起者通常会通过轮询的方式,去一次次发起 I /O 请求,直到读到所需的数据为止。 不过,这样的轮询是对 CPU 计算资源的极大浪费, 因此,非阻塞 I/O 模型单独应用于实际生产的比例并不高。

I/O 多路复用(I/O Multiplexing)

为了避免非阻塞 I/O 模型轮询对计算资源的浪费,同时也考虑到阻塞 I/O 模型的低效,开发人员首选的网络 I/O 模型,逐渐变成了建立在内核提供的多路复用函数 select/poll 等 ( 以及性能更好的 epoll 等函数)基础上的 I/O 多路复用模型

这个模型下,应用线程与内核之间的交互行为模式如下图:

从图中我们看到,在这种模型下,应用线程首先将需要进行的 I/O 操作,都添加到多路复用函数中(这里以 select 为例) , 然后阻塞,等待 select 系统调用返回。 当内核发现有数据到达时,对应的 Socket 具备了通信条件,这时 select 函数返回。 然后用户线程 会针对这个 Socket 再次发起网络 I/O 请求,比如一个 read 操作。 由于数据已就绪,这次 网络 I/O 操作将得到预期的操作结果。

相比于阻塞模型一个线程只能处理一个 Socket 的低效,I/O 多路复用模型中,一个应用线程可以同时处理多个 Socket。 同时,I/O 多路复用模型由内核实现可读 / 可写事件的通知,避免了非阻塞模型中轮询,带来的 CPU 计算资源浪费的问题。

目前,主流网络服务器采用的都是“I/O 多路复用” 模型,有的也结合了多线程。 不过, I/O 多路复用 模型在支持更多连接、 提升 I/O 操作效率的同时,也给使用者带来了不小的复杂度,以至于后面出现了许多高性能的 I/O 多路复用框架,比如: l i bevent、 l i bev、 l i buv等, 以帮助开发者简化开发复杂性,降低心智负担。

非阻塞异步I/O

业内一直有一个疑问:NodeJs 能做后台吗?

其实这个问题早已有答案,阿里的 EggJs 就是 NodeJs 的产物。淘宝双十一的海量并发处理的背后就是 EggJs 的服务。可以说 NodeJs 早已证明了自己能在后端市场占据一席之地。NodeJs 的并发处理能力之所以如此的强,是因为它的 非阻塞异步I/O 机制。

非阻塞异步I/O 中定义了同步与异步两种行为,其中需要I/O操作的就是异步行为,其他的正常操作都是同步。在这种模型下,应用线程与内核之间的交互行为模式如下图:

我们可以看到非阻塞异步I/O 模型最大的特点就是不阻塞。同应用线程遇到异步操作之后,会继续执行同步操作,此时应用与系统两个线程就可以各自工作。等系统数据响应且应用线程同步操作执行完之后,应用就开始继续执行之前的异步操作。

这种模式可以极大地提高了CPU的利用率,并且大幅提高应用的并发处理能力。

总结

今天我们简单地把目前业内几种常见的I/O模型了解了一遍。软件开发不仅仅是业务层的开发,或者API的调用。了解这些 I/O模型的思想设计,无论对我们的底层开发能力,还是代码设计能力都有极大的帮助。

如果你觉得本文对你有一点帮助,麻烦给我点个赞吧~~ 谢谢

おすすめ

転載: juejin.im/post/7147631903875530789