IO模型

一:IO简介
Unix(like)中,一切皆文件。Socket、FIFO、管道、终端都是文件,一切都是流。在信息交换的过程当中,实际都是对这些流进行的数据收发操做,简称I/O操做(系统调用read、write)。而流有不少,因而就用文件描述符(fd)来区分具体是哪一个流。For example,咱们建立了一个socket,系统调用会返回一个fd,对socket的任何操做都是对这个fd的操做(隐隐包含着一种分层与抽象的思想)。web

二:同步异步、阻塞非阻塞
同步与异步是一种通讯机制,涉及到调用方和被调用方(针对应用程序与内核而言)。同步过程当中,进程触发IO操做并等待(阻塞)或者轮询的(非阻塞)去查看IO操做是否完成;异步过程当中,进程触发IO操做之后,直接返回,作本身的事情,IO交给内核来处理,完成后内核通知进程IO完成。同步和异步关注的是程序之间的协做关系。同步分为阻塞和非阻塞,异步则只有非阻塞。
阻塞和非阻塞是一种调用机制,只涉及到调用方(针对单个进程的执行状态),关注的是IO操做的执行。调用方等待IO操做完成后返回则为阻塞;调用方无需等待IO操做完成便返回则为非阻塞,在非阻塞的状况下,调用方经常须要主动去check,得到IO的操做结果。缓存

三:深刻下阻塞
由于一个线程只能处理一个socket的IO,若是想同时处理多个,能够用非阻塞忙轮询的方法,伪代码是这样的:app

for{
    for _,v := range []streams{
        if v has data
        read until unavailable
    }
}

把流(stream)从头至尾读一遍就能处理了,但是这样效率很低,要是全部流都没有IO事件,就浪费了CPU的资源。为了不CPU空转,不让这个线程去检查流是否有IO事件,而是引进一个代理(起初是select,后来是epoll),它能够同时observe许多stream事件,若是没有事件,代理就阻塞,线程就不会去挨个check了。伪代码:异步

for{
    select([]streams) 
    for _,v := range []streams{
        if v has data
        read until unavailable
    }
}

可即使这样,依旧要去作循环,由于select只是告诉线程有IO事件发生,可并无告诉线程是哪一个fd(或者多个),因此select的复杂度是O(n)。epoll(event poll)就解决了这个问题,因此epoll是事件驱动的,由于每一个事件上关联了fd,复杂度也降到了O(1)。socket

for{
    happened_IO := append(happened_IO,epoll_wait(epollfd))
    for _,v := range happened_IO{
      read or write
    }
}

四:几种IO模型
IO发生时(以network IO read为例)涉及到两个系统对象:一个是调用这个IO的process(进程)或者thread(线程),以及两个阶段:一、等待数据准备,二、将数据从内核copy到process中。 IO模型的区别就是在这两个阶段上的差别。async

1)blocking(阻塞)IO
当调用一个系统调用read时,kernel就开始IO的第一个阶段:准备数据,对于network IO来讲,不少时候数据一开始尚未到达(一个TCP包没有接收完整),这个时候kernel就要等待足够的数据到来(这也和缓存IO仍是非缓存IO有关,通常都是缓存IO)。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,就会将数据从系统内存copy到用户内存,而后kernel返回结果,用户进程才接触block状态,从新运行起来。blocking IO的特色就是两个阶段都被block。线程

2)non-blocking(非阻塞)IO
当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,而是马上返回一个error。从用户进程角度来说,他发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。用户进程判断结果是error时,他就知道数据尚未准备好,因而就再次发送read调用。知道kernel中数据准备好了后,而且再次接收到system call后,将数据copy到用户内存。可是这种模型效率很低,上文“深刻下阻塞”有提到。代理

3)multiplexing(多路复用)IO
其实就是select/epoll,也称为event driven IO。select/epoll的好处就是一个thread能够同时处理多个socket的IO,其基本原理就是select/epoll会不断轮询所负责的socket,当某个socket有数据到达了,就通知用户进程。当用户进程调用了select/epoll,整个进程就会被block,同时kernel会observe全部select/epoll负责的socket,任何一个socket中数据准备好以后,select/epoll就会返回,这时用户进程再调用read system call,将数据从kernel copy进用户内存。
multiplexing io和blocking io差异不大,还更差一些,由于他有两个system call,可是他的优点是他能够处理多个connection。因此使用select/epoll的web server不必定处理速度很快,他只是能处理更多链接。code

4)asynchronous(异步)IO
用户进程发起read操做以后,马上就能够开始去作其它的事。而另外一方面,从kernel的角度,当它受到一个asynchronous read以后,首先它会马上返回,因此不会对用户进程产生任何block。而后,kernel会等待数据准备完成,而后将数据拷贝到用户内存,当这一切都完成以后,kernel会给用户进程发送一个signal,告诉它read操做完成了。server

主要参考了Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”的I/O Models

相关文章
相关标签/搜索