io多路复用,是指,io多路复用技术分为三种

  io多路复用,是指,io多路复用技术分为三种

  传统模式传统编程是线性模式:

  start-code block A-code block B-code block C-code block D-输入不同的数据,根据条件语句,可能会将进程改为A-C-E-End。每个程序的运行顺序可能不一样,但是它的控制流程是由输入数据和你写的程序决定的。如果你知道了这个程序当前的运行状态(包括输入数据和程序本身),那么你就知道了它下一步甚至是运行过程的结束。

  事件驱动模型为事件驱动程序模型,其流程大致如下:

  启动-初始化-等待不同于上述传统的编程模式。事件驱动程序启动后,它只是在那里等待。你还在等什么?

  等待事件触发。传统上,编程中有“等待”时间。例如,在代码块D中,您定义了一个input(),它要求用户输入数据。但这和下面的等待不一样。在传统的编程“等待”中,比如input(),你作为一个程序员,知道或者强迫用户输入某个东西,可能是一个数字,也可能是一个文件名。如果用户输入错误,你也需要提醒他,让他重新输入。

  事件驱动程序的等待是完全未知的,不强迫用户输入或做任何事情。只要有事件发生,程序就会做出相应的“反应”。这些事件包括:输入信息、鼠标、敲击键盘上的按键和系统内部定时器触发。

  通常,有以下模型供处理器处理模型:

  收到每个请求后,会创建一个进程来处理该请求。每次收到请求时,都会创建一个新线程来处理该请求。在接收到每个请求后,将其放入一个事件列表中,并让孤独的背包通过非阻塞I/O来处理请求。第三种是协调和事件驱动的方法。

  使用示例比较单线程、多线程和事件驱动的编程模型。下图显示了程序在这三种模式下随时间推移所做的工作。这个程序有三个任务要完成,每个任务在等待I/O操作时阻塞自己。花费在阻塞I/O操作上的时间用灰色方框标记。

  在单线程同步模型中,任务按顺序执行。如果一个任务由于I/O而被阻塞,所有其他任务必须等到它完成后才能依次执行。这种清晰的执行顺序和序列化行为很容易推断出来。如果任务之间互不依赖,但仍然需要相互等待,这将不必要地降低程序的运行速度。

  在多线程版本中,这三个任务在不同的线程中执行。这些线程由操作系统管理,可以在多处理器系统上并行处理,或者在多处理器系统上执行。这使其他线程能够在一个线程阻塞资源时继续执行。这种方法比执行类似功能的同步程序更高效,但程序员必须编写代码来保护共享资源,防止它们被多个线程同时访问。多线程程序更难推断,因为这类程序要通过锁、可重入函数、线程本地存储或其他机制等线程同步机制来处理线程安全问题。如果执行不当,就会出现微妙而痛苦的错误。

  在程序的事件驱动版本中,三个任务交替执行,但仍由单个线程控制。当处理I/O或其他开销较大的操作时,在事件循环中注册一个回调,然后在I/O操作完成时继续执行。回调描述如何处理事件。事件在一个循环中轮询所有事件,当事件到达时,它们被分配给等待处理事件的回调函数。这样,程序可以在不使用额外线程的情况下尽可能多地执行。事件驱动程序比多线程程序更容易推断行为,因为程序员不需要关心线程安全。

  当我们面临以下情况时,事件驱动模型通常是一个不错的选择:

  程序中有很多任务,而且…任务是高度独立的(所以不需要互相通信,或者互相等待)而且…在等待一个事件的时候,有些任务会阻塞。当应用程序需要在任务之间共享可变数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

  网络应用程序通常具有这些特征,这使得它们非常适合事件驱动的编程模型。

  服务器端编程往往需要构建一个高性能的IO模型。常见的

IO模型

有四种:

  IO(BlockingIO:传统的IO模型。非阻塞IO:所有默认创建的套接字都被阻塞。非阻塞IO要求套接字设置为非阻塞。注意这里说的NIO不是Java的NIO(NewIO)库。IO复用:经典的反应器设计模式,有时称为异步阻塞IO,在Java中是选择器,在Linux中是epoll。异步IO(AsynchronousIO):经典的Proactor设计模式,也称为异步非阻塞IO。

  

同步和异步

  它描述了用户线程与内核的交互:同步是指用户线程发起IO请求后,需要等待或轮询内核IO操作,才能继续执行;异步是指用户线程发起IO请求后会继续执行,并在内核IO操作完成时通知用户线程,或者调用用户线程注册的回调函数。

阻塞和非阻塞

  它描述了用户线程调用内核IO操作的方式:阻塞是指IO操作需要完全完成后才能返回用户空间;非阻塞意味着在调用IO操作后,立即向用户返回一个状态值,而不等待IO操作完全完成。此外,RichardStevens在第1卷《Unix网络编程》中提到的信号驱动IO(SignalDrivenIO)模型在本文中并不常用。接下来,我们详细分析了四种常见IO模型的实现原理。为了描述方便,我们以IO的读操作为例。

  

一、

同步阻塞IO

  同步IO模型是最简单的IO模型,当内核正在执行IO操作时,用户线程被阻塞。

  图1同步阻塞IO如图1所示,用户线程通过系统调用read发起IO读操作,从用户空间转移到内核空间。数据包到达后,内核将接收到的数据复制到用户空间,完成读取操作。

  也就是说,在继续处理接收到的数据之前,用户需要等待read将套接字中的数据读入缓冲区。在整个IO请求过程中,用户线程被阻塞,导致用户在发起IO请求时什么都做不了,CPU资源利用不足。

  

二、

同步非阻塞IO

  同步非阻塞IO基于同步阻塞IO,socket设置为非阻塞。这样,用户线程可以在发起IO请求后立即返回。

  图2同步非阻塞IO如图2所示,由于socket是非阻塞的,用户线程发起IO请求时会立即返回。但是没有读取任何数据,用户线程需要不断发起IO请求,直到数据到达,才能真正读取数据,继续执行。

  即用户需要不断调用read,尝试读取socket中的数据,读取成功后再继续处理接收到的数据。在整个IO请求过程中,虽然用户线程可以在每次IO请求后立即返回,但是为了等待数据,仍然需要不断轮询和重复请求,消耗了大量的CPU资源。通常很少直接使用这种模型,但是非阻塞IO的特性在其他IO模型中使用。

  

三、

IO多路复用

  IO复用模式基于内核提供的解复用功能选择。使用select函数可以避免同步非阻塞IO模型中的轮询等待问题。

  图3复用函数select如图3所示,用户首先将需要IO操作的socket添加到select中,然后阻塞并等待select系统调用返回。当数据到达时,套接字被激活,select函数返回。用户线程正式发起读请求,读取数据并继续执行。

  从流程上看,使用select函数进行IO请求和同步阻塞模型没有太大区别,甚至还有添加监听套接字、调用select函数等额外操作,效率更差。不过使用select最大的好处是用户可以在一个线程中同时处理多个socket的IO请求。用户可以注册多个套接字,然后不断调用select读取激活的套接字,可以达到

同一个线程内同时处理多个IO请求的目的

。在同步阻塞模型中,这个目标只能通过多线程来实现。

  套接字被添加到while循环之前的select monitoring,然后select总是在while中被调用以获取激活的套接字。一旦套接字可读,就调用read函数来读取套接字中的数据。

  然而,使用select函数的优点并不仅限于此。虽然上述方法允许在单个线程中处理多个IO请求,但是每个IO请求的进程仍然是阻塞的(阻塞在select函数上),平均时间甚至比同步阻塞IO模型更长。如果用户线程只注册自己感兴趣的socket或IO请求,然后自己做自己的事,等到数据到达后再处理,可以提高CPU利用率。

  IO多路复用模型使用反应器设计模式来实现这种机制。

  图4反应器设计模式如图4所示。EventHandler抽象类表示IO事件处理程序,有IO文件句柄Handle(通过get_handle获得)和操作handle_event(读/写等。)在手柄上。从EventHandler继承的子类可以自定义事件处理程序的行为。Reactor类用于管理EventHandler(注册、删除等。),并使用handle_events实现事件循环。它不断调用同步事件解复用器(通常是内核)的解复用函数select。只要某个文件句柄被激活(读/写等)。),select会返回(block),handle_events会调用与文件句柄关联的事件处理程序的handle_event进行相关操作。

  图5IO复用如图5所示,借助Reactor,用户线程轮询IO操作状态的工作可以交给handle_events事件循环进行处理。用户注册事件处理程序后,线程可以继续做其他工作(异步),而反应器线程负责调用内核的select函数来检查套接字状态。当套接字被激活时,相应的用户线程被通知(或者用户线程的回调函数被执行),handle_event被执行以读取和处理数据。因为select函数被阻塞,所以多路复用IO模型也被称为异步阻塞IO模型。注意,这里的阻塞是指执行select函数时线程被阻塞,而不是套接字。一般在使用IO复用模型时,socket设置为非阻塞,但这不会有影响,因为当用户发起IO请求时,数据已经到了,用户的线程一定不能阻塞。

  用户IO复用模型的伪代码描述如下:

  用户需要重写EventHandler的handle_event函数来读取和处理数据,用户线程只需要向Reactor注册其EventHandler即可。Reactor中handle_events事件循环的伪代码大致如下。

  反应器:handle_events(){

  while(1){

  sockets=select();

  for(socketinsockets){

  get_event_handler(套接字)。handle _ event();

  }

  }

  }

  事件反复调用select获取激活的套接字,然后根据获取的套接字对应的EventHandler执行handle_event函数。

  IO多路复用是最常用的IO模型,但是它的异步不够“彻底”,因为它使用了会阻塞线程的select系统调用。所以IO复用只能叫异步阻塞IO,而不是真正的异步IO。

  

四、

异步IO

  “真正的”异步IO需要操作系统更强的支持。在IO复用模型中,事件循环将文件句柄的状态事件通知给用户线程,用户线程自行读取和处理数据。在异步IO模型中,当用户线程收到通知时,数据已经被内核读取并放入用户线程指定的缓冲区。IO完成后,内核可以通知用户线程直接使用。

  异步IO模型使用Proactor设计模式来实现这一机制。

  图6前摄器设计模式如图6所示。Proactor模式和Reactor模式结构相似,但用户使用方式有很大区别。在反应器模式中,用户线程向反应器对象注册感兴趣的事件以进行侦听,然后在事件被触发时调用事件处理程序。在主动者模式下,用户线程注册异步操作(读/写等。)、Proactor和CompletionHandler(当操作完成时)传递给AsynchronousOperationProcessor。AsynchronousOperationProcessor提供了一组异步操作API(读/写等。)供用户通过使用门面模式使用。当用户线程调用异步API时,它将继续执行自己的任务。AsynchronousOperationProcessor会打开独立的内核线程来执行异步操作,从而实现真正的异步。异步IO操作完成后,AsynchronousOperationProcessor将用户线程注册的Proactor和CompletionHandler连同异步操作一起取出,然后将CompletionHandler连同IO操作的结果数据一起转发给Proactor,由Proactor负责回调每个异步操作的事件完成处理函数handle_event。虽然Proactor模式下的每个异步操作都可以绑定一个Proactor对象,但一般在操作系统中,Proactor都是以Singleton模式实现的,便于操作完成事件的集中分发。

  图7异步IO如图7所示,在异步IO模型中,用户线程直接使用内核提供的异步IOAPI发起读请求,发起后立即返回继续执行用户线程代码。然而此时用户线程已经向内核注册了调用的AsynchronousOperation和CompletionHandler,然后操作系统启动一个独立的内核线程来处理IO操作。当read请求的数据到达时,内核负责读取socket中的数据,并写入用户指定的缓冲区。最后内核将读取的数据和用户线程注册的CompletionHandler分发到内部Proactor,Proactor通知用户线程IO完成(一般通过调用用户线程注册的完成事件处理函数)完成异步IO。

  线程使用的异步IO模型的伪代码描述如下:

  voidUserCompletionHandler:handle _ event(buffer){

  进程(缓冲区);

  }

  {

  aio_read(socket,newUserCompletionHandler);

  }

  用户需要重写CompletionHandler的handle_event函数来处理数据。参数buffer表示Proactor已经准备好的数据。用户线程直接调用内核提供的异步IOAPI,注册重写的CompletionHandler。

  相对于IO复用模型,异步IO并不是很常见,很多高性能并发服务程序使用IO复用模型的多线程任务处理架构基本可以满足需求。况且目前操作系统对异步IO的支持还不是特别完善,更多的是采用IO复用模型来模拟异步IO(IO事件触发时不直接通知用户线程,而是读写后将数据放入用户指定的缓冲区)。Java7之后已经支持异步IO,有兴趣的读者可以试试。

  I/O复用:通过一种机制,对多个描述符进行监控,一旦一个描述符就绪(通常是读或写),就可以通知程序执行相应的读写操作。

  Linux中的Select、poll、epoll都是IO复用机制。

  有一个select模块python python,提供了select、poll、epoll三种方法实现复用。

  注意:

  windows Python:select MAC Python:select Linux Python:select、poll、epollI/O操作、网络操作、文件操作、终端操作都是I/O操作。windows只支持socket操作,其他系统支持其他io操作。

  更多:Python目录

io多路复用,是指,io多路复用技术分为三种