惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 布谷鸟过滤器 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading 布谷鸟过滤器 Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式
I/O 模型的阻塞/非阻塞, 同步/异步
2022-01-09 · via 蛮荆

2022-01-09 计算机网络 高性能网络编程

I/O 模型

Unix 有五种 I/O 模型

  1. 阻塞式 I/O
  2. 非阻塞式 I/O
  3. I/O 多路复用(select, poll, epoll)
  4. 信号驱动式 I/O
  5. 异步 I/O(AIO)

1. 阻塞式 I/O

应用进程执行系统调用时被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。

阻塞不意味着整个操作系统都被阻塞,在阻塞的过程中,其它应用进程还可以执行,所以阻塞本身不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。

下面将 阻塞式 I/O 工作流程翻译为简单的伪代码。

while True:
    # 阻塞等待客户端连接
    connection, client_address = sock.accept()
    try:
        while True:
            # 阻塞等待数据
            # 读取数据
            data = connection.recv(16)
            # 执行其他操作

2. 非阻塞式 I/O

应用进程执行系统调用时,内核直接返回一个错误码,然后应用进程可以继续向下运行,但是需要不断的执行系统调用来获取 I/O 操作是否完成,也称为轮询(polling)。

由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。

下面将 非阻塞式 I/O 工作流程翻译为简单的伪代码。

while True:
    try:
        # 内核直接返回一个错误吗
        connection, client_address = sock.accept()
        # 继续向下执行
        connection.setblocking(0)
    except BlockingIOError:
        # 处理错误
        ...

    try:
        # 读取数据
        data = connection.recv(16)
        # 执行其他操作
        ...
    except BlockingIOError:
        # 处理错误
        ...

3. I/O 多路复用

使用 select, poll, epoll 等待多个套接字 (文件描述符) 中的任何一个或多个变为就绪,等待过程会阻塞,当某一个套接字就绪后,再把数据从内核空间 (缓冲期) 复制数据到用户空间 (进程) 。当然,epoll 的工作方式和 select, poll 有些差异,不过这里先不做细节上的深究,统一当作 I/O 多路复用的实现来看待。

这种模型可以让单个 进程/线程 具有处理多个 I/O 事件的能力,也称为事件驱动 I/O。

如果一个 Web Server 没有 I/O 多路复用,那么每一个 Socket 连接都需要创建一个线程去处理,如果同时有几万个连接,那么就需要创建相同数量的线程 (可能导致的后果就是操作系统直接崩溃),相比之下,I/O 多路复用不需要多个进程/线程创建、上下文切换的开销,系统负载更小。

下面将 I/O 多路复用 工作流程翻译为简单的伪代码。

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 8080)

...

inputs = [sock]
outputs = []

while inputs:
    # 阻塞等待套接字就绪
    readable, writable, exceptional = select.select(inputs, outputs, inputs)

    # 获取到已经就绪的套接字
    # 遍历处理读事件
    for s in readable:
        ...

    # 遍历处理写事件
    for s in writable:
        ...

    # 遍历处理其他事件
    for s in exceptional:
        ...

4. 信号驱动 I/O

应用进程执行系统调用时,内核直接返回,然后应用进程可以继续向下运行,也就是说等待数据阶段应用进程是非阻塞的。

内核在数据到达时向应用进程发送信号,应用进程收到信号之后,在信号处理程序中执行系统调用,将数据从内核空间 (缓冲期) 复制数据到用户空间 (进程),这种模型的 CPU 利用率会比较高。

# 信号回调函数
def handler(signum, frame):
    # 读取数据
    data, addr = sock.recvfrom(1024)
    # 执行其他操作
    ...

# 初始化 socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8080))

...

# 注册信号回调函数
signal.signal(signal.SIGIO, handler)

...

while True:
    # 等待信号通知
    signal.pause()

5. 异步 I/O

应用进程执行系统调用时,内核直接返回,然后应用进程可以继续向下运行,不会被阻塞,内核会在所有操作 (包括从内核空间复制数据到用户空间) 完成之后向应用进程发送信号。

异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O (数据复制) 已经完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O (数据复制) 。

# 异步回调函数
# 通知应用进程 I/O (数据复制) 已经完成
async def handle_client(reader, writer):
    while True:
        # 直接读取数据即可
        data = await reader.read(100)

        # 执行其他操作
        ...

    writer.close()

async def main():
    # 监听端口并注册异步回调函数
    server = await asyncio.start_server(handle_client, 'localhost', 10000)

阻塞/非阻塞,同步/异步

如何判断一个 I/O 模型是同步还是异步?

I/O 事件数据在内核空间和用户空间来回复制时,是否会阻塞当前线程?

如果会阻塞当前线程,则为同步 I/O, 否则就是异步 I/O。

所以前文中提到的 5 种 I/O 模型中,只有最后 1 种 异步 I/O 模型 是真正的异步 I/O, 其他 4 种都是同步 I/O。

一个 I/O 读取操作通常包括两个步骤:

  1. 等待网络数据到达网卡(读就绪) -> 等待网卡可写(写就绪) –> 读取/写入到内核缓冲区
  2. 从内核空间缓冲区复制数据 –> 用户空间(读), 或者 从用户空间 (进程) 复制数据 -> 内核缓冲区(写)

4 种同步 I/O 模型的主要区别在 I/O 操作的第一步: 等待网络数据到达网卡(读就绪) -> 等待网卡可写(写就绪) –> 读取/写入到内核缓冲区,除了 阻塞式 I/O 模型外,其他 3 种同步 I/O 模型在第一步不会发生阻塞。

换句话说,只有 阻塞式 I/O 模型是阻塞的,其他 3 种模型都是非阻塞的。


小结

阻塞 / 非阻塞的主语是 I/O 操作调用者(应用进程),而同步 / 异步的主语是 I/O 操作执行者(操作系统)。

I/O 模型 是否阻塞 是否同步
阻塞式 I/O
非阻塞式 I/O
I/O 多路复用
信号驱动式 I/O
异步 I/O

Reference