IO复用

IO复用

   概要

   IO 复用(I/O Multiplexing)是一种用于同时处理多个 I/O 操作而不会阻塞程序执行的技术。它使得一个进程或线程可以同时监视多个 I/O 操作,以便在其中的某个操作变得可读、可写或者发生异常时,能够立即响应。

   一、重要概念

   1. 非阻塞 I/O

   在传统的阻塞 I/O 模型中,一个 I/O 操作(如读取数据)会阻塞当前线程,直到操作完成。IO 复用允许线程或进程同时处理多个 I/O 操作而不会被阻塞。

   2. 事件驱动

   IO 复用机制基于事件驱动模型。当监视的 I/O 操作的状态发生变化时(例如,有数据可读),系统会触发事件,进程或线程可以处理这些事件,而不是等待某个操作完成。

   3. 文件描述符

   1)文件描述符是操作系统用来标识和管理打开文件、套接字、管道、终端等 I/O 资源的整数值。每个进程在其打开文件表中使用文件描述符来访问这些资源。

   2)文件描述符是一个非负整数,操作系统通过这个整数来跟踪进程对这些 I/O 资源的访问

   二、常见的IO复用机制

   select、poll 和 epoll 是三种用于实现 IO 复用的系统调用,它们允许程序在一个线程中监视多个文件描述符(如网络连接、文件等),以便在其中一个或多个文件描述符变得可读、可写或发生异常时进行处理。这些系统调用主要用于提高处理大量并发 I/O 操作的效率。

   1.  select

   Linux很早就提供了 select 系统调用。select 是最早的 IO 复用机制,允许进程监视多个文件描述符,并在这些文件描述符的状态发生变化时通知进程。

   优点:简单、易于理解。

   缺点:有文件描述符数量限制,性能随着监视的文件描述符数量增加而降低。

   2. poll

   poll 是 select 的改进版,支持更多的文件描述符。

   优点:没有文件描述符数量限制。

   缺点:性能瓶颈依然存在,因为每次调用 poll 都需要遍历所有的文件描述符。意思就是它需要循环检测连接是否有事件。这样问题就来了,如果服务器有100万个连接,在某一时间只有一个连接向服务器发送了数据,select/poll需要做循环100万次,其中只有1次是命中的,剩下的99万9999次都是无效的,白白浪费了CPU资源。

   3. epoll

   epoll是Linux 特有的 IO 复用机制,可以维持无限数量的连接,而且无需轮询。解决了 select 和 poll 的性能问题。

   现在各种高并发异步IO的服务器程序都是基于epoll实现的,比如Nginx、Node.js、Erlang、Golang。像 Node.js 这样单进程单线程的程序,都可以维持超过100wTCP连接,全部归功于epoll技术。

   优点:高效、支持大量文件描述符。

   缺点:只能在 Linux 上使用。

   三、 I/O 多路复用模型

    I/O 多路复用模型是利用多路 I/O 复用机制来实现高效处理多路 I/O 操作的一种设计模型。它强调的是设计上的思路和应用模式,通常用于描述系统如何通过这种机制实现高并发 I/O 处理。

   1. 重要概念

   1)多路:  指的是同时处理多个 I/O 流或网络连接。可以是多个文件描述符、套接字、管道等。

   2)复用:  指的是使用同一个线程来处理这些多个 I/O 流的事件。通过复用线程,减少了线程切换的开销,提高了资源的利用率。

   2. 工作原理

   1)  注册事件:将需要监控的 I/O 流(例如,网络套接字)注册到 I/O 复用机制中(如 select、poll、epoll)。

   2)  阻塞等待:线程会在 I/O 复用机制(如 select、poll、epoll_wait)上等待,直到有一个或多个 I/O 操作(例如:数据到达或新连接请求)需要处理时,它才会被唤醒。

   3)  事件检测

   select 和 poll:这些机制会检测所有注册的 I/O 流,通常会遍历所有的流来检查是否有事件发生。这种方法在大规模连接的情况下可能会有性能瓶颈,因为它需要检查所有的流。

   epoll:这种机制更高效,它只检查那些真正发生事件的流,从而减少了不必要的遍历和处理,尤其在高并发场景下表现更佳。

   4)  处理事件:一旦检测到有 I/O 事件发生,线程会被唤醒并处理这些事件。处理包括读取数据、写入数据、接受连接等。

   3.  优势

   1) 高效处理:多路 I/O 复用可以在单线程中高效地处理多个并发的 I/O 操作,减少了线程上下文切换的开销。

   2) 节省资源:通过复用线程,避免了为每个连接创建独立线程的资源开销,适用于大规模连接场景。

   3) 降低延迟:减少了无用操作和线程切换,提高了整体的处理效率,尤其在 I/O 操作占主导的场景下(如 Redis 的内存操作)。

   4.  问题:Redis 为什么使用单进程单线程方式也这么快?

   Redis 采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由 C 语言编写。官方提供的数据是可以达到10w+的 qps。这个数据不比采用单进程多线程的同样基于内存的 KV 数据库 Memcached 差。

   Redis 快的主要原因有:

  • 完全基于内存
  • 数据结构简单,对数据操作也简单
  • 使用多路 I/O 复用模型

   展开来说:

  •   Redis 使用 I/O 复用技术(如 epoll)来处理大量的并发请求,主要通过高效的内存操作来提供高吞吐量。
  •   Redis 的高性能不仅来源于 I/O 复用,还因为其在内存中进行数据处理,这比磁盘操作要快得多。

   5)总结

   多路 I/O 复用模型的核心在于利用单线程高效处理多个 I/O 事件,通过阻塞等待和事件通知机制避免了对每个连接创建独立线程的开销。通过高效的事件检测和处理,能够显著提高程序的并发处理能力和资源利用率。

   四、Reactor 模型

   Reactor 模型是一种高层次的设计模式,建立在多路 I/O 复用模型之上,用于构建高并发的事件驱动系统。它通过监听事件并将这些事件分发给对应的处理器来实现异步处理。

   Reactor 本身并不处理数据,而是负责监视多个 I/O 流(如套接字)的事件变化。当发生感兴趣的事件(如数据到达或连接请求)时,Reactor 会将这些事件分发给相应的处理器。这样可以在高效处理大量并发 I/O 操作的同时,避免了阻塞线程。 

   具体步骤如下:

  • 事件检测:使用 epoll、select 等 IO 复用机制来监视多个文件描述符。
  • 事件分派:当有事件发生时,将事件分派给相应的处理器。
  • 事件处理:处理器处理事件(例如,读写数据)。

   Reactor 有4个核心的操作:

   1)添加 Add:将文件描述符添加到事件分发器进行监控。

   2)设置 Set:定义和修改文件描述符的事件类型(如可读、可写)。

   3)移除 Del:从事件分发器中移除文件描述符,停止监控。

   4)处理 Callback:在事件发生时执行预定义的回调函数

   Reactor 模型还可以与多进程、多线程结合起来用,既实现异步非阻塞IO,又利用到多核。目前流行的异步服务器程序都是这样的方式:如

  •    Nginx:多进程Reactor
  •    Nginx+Lua:多进程Reactor+协程
  •    Golang:单线程Reactor+多线程协程
  •    Swoole:多线程Reactor+多进程Worker
posted @   欢乐豆123  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示