2014-11-26 19:24:25 zhangbinalan 阅读数 257

1:BIO(Blocking IO)

     同步阻塞式IO

     在JDK1.4之前,服务端先建立一个ServerSocket,然后客户端Socket请求连接时,服务端建立一个单独的线程处理这个请求。即:一个连接要求Server对应一个处理线程。

2:NIO(New IO)

     BIO中一个连接建立后,需要一个线程处理。会占用或者浪费过多的资源。

     NIO中一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面。所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理。

     NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

3:NIO2/AIO

     AIO基于Proactor模式;与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数


参考:

http://qindongliang.iteye.com/blog/2018539

2018-07-01 12:43:36 z_ryan 阅读数 2965

  为了更好的理解五种IO模型,我们先来说一下几个概念:同步,异步,阻塞和非阻塞。

同步和异步

  这两个概念与消息的通知机制有关。

同步

  所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。比如,调用readfrom系统调用时,必须等待IO操作完成才返回。

异步

  异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。比如:调用aio_read系统调用时,不必等IO操作完成就直接返回,调用结果通过信号来通知调用者。

阻塞与非阻塞

  阻塞与非阻塞与等待消息通知时的状态有关。

阻塞 

  阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。  
  阻塞和同步是完全不同的概念。首先,同步是对于消息的通知机制而言,阻塞是针对等待消息通知时的状态来说的。而且对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。

非阻塞

  非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回,并设置相应的errno。
  虽然表面上看非阻塞的方式可以明显的提高CPU的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的CPU执行时间能不能补偿系统的切换成本需要好好评估。

事例

  以小明下载文件为例,对上述概念做一梳理:
   
①、同步阻塞:小明一直盯着下载进度条,到 100% 的时候就完成。
 
  同步:等待下载完成通知;
  阻塞:等待下载完成通知过程中,不能做其他任务处理;

②、同步非阻塞:小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。

  同步:等待下载完成通知;
  非阻塞:等待下载完成通知过程中,去干别的任务了,只是时不时会瞄一眼进度条;【小明必须要在两个任务间切换,关注下载进度】

③、异步阻塞:小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音(看起来很傻,不是吗)。

  异步:下载完成“叮”一声通知;
  阻塞:等待下载完成“叮”一声通知过程中,不能做其他任务处理;

④、异步非阻塞:仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。
  
  异步:下载完成“叮”一声通知;
  非阻塞:等待下载完成“叮”一声通知过程中,去干别的任务了,只需要接收“叮”声通知。

五种IO模型

阻塞式I/O
非阻塞式I/O
I/O复用(select,poll,epoll等)
信号驱动式I/O(SIGIO)
异步I/O(POSIX的aio_系列函数)

IO执行的两个阶段

  在Linux中,对于一次读取IO的操作,数据并不会直接拷贝到程序的程序缓冲区。通常包括两个不同阶段:
 (1)等待数据准备好,到达内核缓冲区;
 (2)从内核向进程复制数据。
  对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。

阻塞式I/O模型:

  同步阻塞 IO 模型是最常用、最简单的模型。在linux中,默认情况下,所有套接字都是阻塞的。 下面我们以阻塞套接字的recvfrom的的调用图来说明阻塞:
这里写图片描述
  进程调用一个recvfrom请求,但是它不能立刻收到回复,直到数据返回,然后将数据从内核空间复制到程序空间。
  在IO执行的两个阶段中,进程都处于blocked(阻塞)状态,在等待数据返回的过程中不能做其他的工作,只能阻塞的等在那里。
  
优缺点:
  优点是简单,实时性高,响应及时无延时,但缺点也很明显,需要阻塞等待,性能差;

非阻塞式I/O:

  与阻塞式I/O不同的是,非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error(EAGAIN 或 EWOULDBLOCK)。进程在返回之后,可以处理其他的业务逻辑,过会儿再发起recvform系统调用。采用轮询的方式检查内核数据,直到数据准备好。再拷贝数据到进程,进行数据处理。
  在linux下,可以通过设置socket套接字选项使其变为非阻塞。下图是非阻塞的套接字的recvfrom操作
这里写图片描述
  如上图,前三次调用recvfrom请求,但是并没有数据返回,所以内核返回errno(EWOULDBLOCK),并不会阻塞进程。但是当第四次调用recvfrom,数据已经准备好了,然后将它从内核空间拷贝到程序空间,处理数据。
  在非阻塞状态下,IO执行的等待阶段并不是完全的阻塞的,但是第二个阶段依然处于一个阻塞状态。
  
同步非阻塞方式相比同步阻塞方式
  优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。
  缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

I/O多路复用(select,poll,epol):

  IO 多路复用的好处就在于单个进程就可以同时处理多个网络连接的IO。它的基本原理就是不再由应用程序自己监视连接,取而代之由内核替应用程序监视文件描述符。
  以select为例,当用户进程调用了select,那么整个进程会被阻塞,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程。如图:
这里写图片描述
  这里需要使用两个system call (select 和 recvfrom),而阻塞 IO只调用了一个system call (recvfrom)。所以,如果处理的连接数不是很高的话,使用IO复用的服务器并不一定比使用多线程+非阻塞阻塞 IO的性能更好,可能延迟还更大。IO复用的优势并不是对于单个连接能处理得更快,而是单个进程就可以同时处理多个网络连接的IO。
  实际使用时,对于每一个socket,都可以设置为非阻塞。但是,如上图所示,整个用户的进程其实是一直被阻塞的。只不过进程是被select这个函数阻塞,而不是被IO操作给阻塞。所以IO多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用(如recvfrom)。

优势
  与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源。
主要应用场景
  ①、服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;
  ②、服务器需要同时处理多种网络协议的套接字,如同时处理TCP和UDP请求;
  ③、服务器需要监听多个端口或处理多种服务;
  ④、服务器需要同时处理用户输入和网络连接。

信号驱动式I/O

  允许Socket进行信号驱动IO,并注册一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。如下图:
这里写图片描述
  阻塞在IO操作的第二阶段

异步I/O模型:

  上述四种IO模型都是同步的。相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,就可以去处理其他的逻辑了,无论内核数据是否准备好,都会直接返回给用户进程,不会对进程造成阻塞。等到数据准备好了,内核直接复制数据到进程空间,然后从内核向进程发送通知,此时数据已经在用户空间了,可以对数据进行处理了。
  在 Linux 中,通知的方式是 “信号”,分为三种情况:
  ①、如果这个进程正在用户态处理其他逻辑,那就强行打断,调用事先注册的信号处理函数,这个函数可以决定何时以及如何处理这个异步任务。由于信号处理函数是突然闯进来的,因此跟中断处理程序一样,有很多事情是不能做的,因此保险起见,一般是把事件 “登记” 一下放进队列,然后返回该进程原来在做的事。
  ②、如果这个进程正在内核态处理,例如以同步阻塞方式读写磁盘,那就把这个通知挂起来了,等到内核态的事情忙完了,快要回到用户态的时候,再触发信号通知。
  ③、如果这个进程现在被挂起了,例如陷入睡眠,那就把这个进程唤醒,等待CPU调度,触发信号通知。
这里写图片描述
  IO两个阶段,进程都是非阻塞的

五种IO模型比较

这里写图片描述
  其实前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。相反,异步I/O模型在这等待数据和接收数据的这两个阶段里面都是非阻塞的,可以处理其他的逻辑用户进程将整个IO操作交由内核完成,内核完成后会发送通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

参考
聊聊同步、异步、阻塞与非阻塞:https://www.jianshu.com/p/aed6067eeac9
《unix网络编程 :卷一》

2019-07-24 15:20:00 weixin_30616969 阅读数 9

转自 https://www.cnblogs.com/cobbliu/articles/8487161.html

 

POSIX AIO 是在用户控件模拟异步 IO 的功能,不需要内核支持,而 linux AIO 则是 linux 内核原声支持的异步 IO 调用,行为更加低级

 

关于 linux IO 模型及 AIO、POSIX AIO 的简介,请参看:

POSIX AIO -- glibc 版本异步 IO 简介

 

libaio 实现的异步 IO 主要包含以下接口:

libaio 实现的异步 IO
函数 功能 原型
io_setup 创建一个异步IO上下文(io_context_t是一个句柄) int io_setup(int maxevents, io_context_t *ctxp);
io_destroy 销毁一个异步IO上下文(如果有正在进行的异步IO,取消并等待它们完成) int io_destroy(io_context_t ctx);
io_submit 提交异步IO请求 long io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);
io_cancel 取消一个异步IO请求 long io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);
io_getevents 等待并获取异步IO请求的事件(也就是异步请求的处理结果) long io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

 

 

iocb 结构

 

struct iocb主要包含以下字段:

 1 struct iocb
 2 {
 3     /*
 4      * 请求类型
 5      * 如:IOCB_CMD_PREAD=读、IOCB_CMD_PWRITE=写、等
 6      */
 7     __u16     aio_lio_opcode;
 8     /*
 9      * 要被操作的fd
10      */
11     __u32     aio_fildes;
12     /*
13      * 读写操作对应的内存buffer
14      */
15     __u64     aio_buf;
16     /*
17      * 需要读写的字节长度
18      */
19     __u64     aio_nbytes;
20     /*
21      * 读写操作对应的文件偏移
22      */
23     __s64     aio_offset;
24     /*
25      * 请求可携带的私有数据
26      * 在io_getevents时能够从io_event结果中取得)
27      */
28     __u64     aio_data;
29     /*
30      * 可选IOCB_FLAG_RESFD标记
31      * 表示异步请求处理完成时使用eventfd进行通知
32      */
33     __u32     aio_flags;
34     /*
35      * 有IOCB_FLAG_RESFD标记时,接收通知的eventfd
36      */
37     __u32     aio_resfd;
38 }

 

 
io_event 结构
 1 struct io_event
 2 {
 3     /*
 4      * 对应iocb的aio_data的值
 5      */
 6     __u64     data;
 7     /*
 8      * 指向对应iocb的指针
 9      */
10     __u64     obj;
11     /*
12      * 对应IO请求的结果
13      * >=0: 相当于对应的同步调用的返回值;<0: -errno
14      */
15     __s64     res;
16 }

 

aio_context_t 即 AIO 上下文句柄,该结构体对应内核中的一个 struct kioctx 结构,用来给一组异步 IO 请求提供一个上下文环境,每个进程可以有多个 aio_context_t,io_setup 的第一个参数声明了同时驻留在内核中的异步 IO 上下文数量

kioctx 结构主要包含以下字段:

 1 struct kioctx
 2 {
 3     /*
 4      * 调用者进程对应的内存管理结构
 5      * 代表了调用者的虚拟地址空间
 6      */
 7     struct mm_struct*     mm;
 8     /*
 9      * 上下文ID,也就是io_context_t句柄的值
10      * 等于ring_info.mmap_base
11      */
12     unsigned long         user_id;
13     /*
14      * 属于同一地址空间的所有kioctx结构通过这个list串连起来
15      * 链表头是mm->ioctx_list
16      */
17     struct hlist_node     list;
18     /*
19      * 等待队列
20      * io_getevents系统调用可能需要等待
21      * 调用者就在该等待队列上睡眠
22      */
23     wait_queue_head_t     wait;
24     /*
25      * 进行中的请求数目
26      */
27     int                   reqs_active;
28     /*
29      * 进行中的请求队列
30      */
31     struct list_head      active_reqs;
32     /*
33      * 最大请求数
34      * 对应io_setup调用的int maxevents参数
35      */
36     unsigned              max_reqs;
37     /*
38      * 需要aio线程处理的请求列表
39      * 某些情况下,IO请求可能交给aio线程来提交
40      */
41     struct list_head      run_list;
42     /*
43      * 延迟任务队列
44      * 当需要aio线程处理请求时,将wq挂入aio线程对应的请求队列
45      */
46     struct delayed_work   wq;
47     /*
48      * 存放请求结果io_event结构的ring buffer
49      */
50     struct aio_ring_info  ring_info;
51 }

其中,aio_ring_info 结构用于存放请求结果 io_event 结构的 ring buffer,主要包含以下字段:

 
1 struct aio_ring_info
2 {
3     unsigned long   mmap_base;  // ring buffer 的首地址
4     unsigned long   mmap_size;  // ring buffer 空间大小
5     struct page**   ring_pages; // ring buffer 对应的 page 数组
6     long            nr_pages;   // 分配空间对应的页面数目
7     unsigned        nr;         // io_event 的数目
8     unsigned        tail;       // io_event 的存取游标
9 }

 

 

aio_ring_info 结构中,nr_page * PAGE_SIZE = mmap_size

以上数据结构都是在内核地址空间上分配的,是内核专有的,用户程序无法访问和使用

但是 io_event 结构是内核在用户地址空间上分配的 buffer,用户可以修改,但是首地址、大小等信息都是由内核维护的,用户程序通过 io_getevents 函数修改

 

io_setup 函数创建了一个 AIO 上下文,并通过值-结果参数 aio_context_t 类型指针返回其句柄

io_setup 调用后,内核会通过 mmap 在对应的用户地址空间分配一段内存,由 aio_ring_info 结构中的 mmap_base、mmap_size 描述这个映射对应的位置和大小,由 ring_pages、nr_pages 描述实际分配的物理内存页面信息,异步 IO 完成后,内核会将异步 IO 的结果写入其中

 

在 mmap_base 指向的用户地址空间上,会存放着一个 struct aio_ring 结构,用来管理 ring buffer,主要包含以下字段:

1 unsigned    id;     // 等于 aio_ring_info 中的 user_id
2 unsigned    nr;     // 等于 aio_ring_info 中的 nr
3 unsigned    head;   // io_events 数组队首
4 unsigned    tail;   // io_events 数组游标
5 unsigned    magic;  // 用于确定数据结构有没有异常篡改
6 unsigned    compat_features;
7 unsigned    incompat_features;
8 unsigned    header_length;  // aio_ring 结构大小
9 struct io_event *io_events; // io_event buffer 首地址

这个数据结构存在于用户地址空间中,内核作为生产者,在 buffer 中放入数据,并修改 tail 字段,用户程序作为消费者从 buffer 中取出数据,并修改 head 字段

 

每一个请求用户都会创建一个 iocb 结构用于描述这个请求,而对应于用户传递的每一个 iocb 结构,内核都会生成一个与之对应的 kiocb 结构,并只该结构中的 ring_info 中预留一个 io_events 空间,用于保存处理的结果 

 
 1 struct kiocb
 2 {
 3     struct kioctx*      ki_ctx;           /* 请求对应的kioctx(上下文结构)*/
 4     struct list_head    ki_run_list;      /* 需要aio线程处理的请求,通过该字段链入ki_ctx->run_list */
 5     struct list_head    ki_list;          /* 链入ki_ctx->active_reqs */
 6     struct file*        ki_filp;          /* 对应的文件指针*/
 7     void __user*        ki_obj.user;      /* 指向用户态的iocb结构*/
 8     __u64               ki_user_data;     /* 等于iocb->aio_data */
 9     loff_t              ki_pos;           /* 等于iocb->aio_offset */
10     unsigned short      ki_opcode;        /* 等于iocb->aio_lio_opcode */
11     size_t              ki_nbytes;        /* 等于iocb->aio_nbytes */
12     char __user *       ki_buf;           /* 等于iocb->aio_buf */
13     size_t              ki_left;          /* 该请求剩余字节数(初值等于iocb->aio_nbytes)*/
14     struct eventfd_ctx* ki_eventfd;       /* 由iocb->aio_resfd对应的eventfd对象*/
15     ssize_t (*ki_retry)(struct kiocb *);  /*由ki_opcode选择的请求提交函数*/
16 }

 

这以后,对应的异步读写请求就通过调用 file->f_op->aio_read 或 file->f_op->aio_write 被提交到了虚拟文件系统,与普通的文件读写请求非常类似,但是提交完后 IO 请求立即返回,而不等待虚拟文件系统完成相应操作

对于虚拟文件系统返回 EIOCBRETRY 需要重试的情况,内核会在当前 CPU 的 aio 线程中添加一个任务,让 aio 完成该任务的重新提交

 

 

 

从上图中的流程就可以看出,linux 版本的 AIO 与 POSIX 版本的 AIO 最大的不同在于 linux 版本的 AIO 实际上利用了 CPU 和 IO 设备异步工作的特性,与同步 IO 相比,很大程度上节约了 CPU 资源的浪费

而 POSIX AIO 利用了线程与线程之间的异步工作特性,在用户线程中实现 IO 的异步操作

 

POSIX AIO 支持非 direct-io,而且实现非常灵活,可配置性很高,可以利用内核提供的page cache来提高效率,而 linux 内核实现的 AIO 就只支持 direct-io,cache 的工作就需要用户进程考虑了

转载于:https://www.cnblogs.com/yi-mu-xi/p/11238220.html

2019-07-03 09:09:41 qldd 阅读数 83

相同点:都使用I/O复用模型

区别:

1、支持一个进程所能打开的最大连接数不同。

select最小,单个进程所能打开的最大连接数有FD_SETSIZE宏定义。

poll使用链表无最大限制,

epoll有限制但很大,1g内存支持10w个连接。

2、FD(文件描述符)剧增后的I/O效率问题。

selec、pollt使用遍历模式,每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。

epoll只关注活跃的socket。因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback。socket活跃较少的情况下没有问题,但如果所有socket都很活跃的情况下,可能会有性能问题。

3、消息传递方式

select 、poll需要将数据从内核拷贝到用户空间。

epoll通过内核和用户空间共享一块内存来实现。

4、触发模式不同

select(),poll()模型都是水平触发模式,信号驱动IO是边缘触发模式。

epoll()模型即支持水平触发,也支持边缘触发,默认是水平触发。

 

总结:

在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点:

1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

 

2015-11-30 23:13:56 seavisiongao 阅读数 339
前言


一个socket进程进行一次read可以分成两个阶段,等待数据是否准备好,以及数据从内核copy到用户空间。 我们举个例子,肚子饿了要去小吃街吃拉面,在我们正式开始吃面之前需要1.先等拉面师傅做好面,2.然后把做好的面放到我们的桌子上。


阻塞和非阻塞


阻塞和非阻塞,主要是针对事情的本身,指做一件事后能不能返回一个结果。阻塞就是一直等待到事情做完才返回,非阻塞就是立马返回一个消息(没做好或者已经做好)。如果是阻塞进程read一直等待,进程控制权就没了,要阻塞两个阶段,数据到来和数据从内核copy到进程缓冲区。非阻塞则是read一发出立马返回一个错误信息,程序里必须不断去read直到数据到达返回可读消息,然后进程阻塞把数据从内核缓冲区copy到进程缓冲区,实际阻塞的是后面取数据的阶段。继续上面的例子,下单之后等待师傅做面的过程,我们可以一直傻乎乎地在旁边等着师傅做好。也可以先去其他地方玩玩,然后时不时一会儿跑回来问下师傅有没有做好,有没有做好...前者就是阻塞,后者就是非阻塞。等到师傅师傅做好了,我把面端回来到桌子上,这部分也是阻塞的。


对于阻塞和非阻塞而言,如果针对一个IO,非阻塞根本没阻塞的效率高。而且相对于阻塞而言,非阻塞是轮询要发出很多次系统调用的,而且很多次都是空轮询,看起来毫无优势可言。但是非阻塞IO因为等待数据阶段是非阻塞的,所以可以同时进行很多个IO操作。 继续上面的例子,我点完面之后还想去吃小笼包,还有奶茶。如果是阻塞,那么我必须等面做好了,我再去奶茶店等奶茶,等奶茶好了再去等小笼包。如果是非阻塞,我点了面后,跑到奶茶店点奶茶,然后再去小笼包店点小笼包,然后开始轮着跑三家店是否准备好了,够累的的吧?


IO多路复用


尽管非阻塞支持同时多IO,但是资源上还是很浪费,存在很多空轮询。于是出现了一种新的IO模型,叫做IO多路复用模型,最早的就是select模型和poll模型,两者开始的区别只是平台不同,而且前者有最大监控描述符限制,但是功能上差不多。select模型支持一次性监控一大堆感兴趣的IO事件发生的描述符集合,一旦有一个或多个IO准备就绪,就返回所有描述符集合。然后扫描返回列表,找到有可读或者可写的描述符,可能是一个也可能多个,进行相应第二阶段读取或者写入数据。select模型调用的时候也是阻塞的,只是它支持多路IO前提下减少了不必要的空轮询。 继续上面的例子,我为了能吃面和奶茶,小笼包三家店轮着跑而累的气喘吁吁,这时候小吃街上多了一块大屏幕,里面有各个店准备小吃的状态,比如小笼包正在做,面已经做好了,等等,我就是只需要看着大屏幕就可以了,然后筛选下哪些是已经做好了,我就去店里面取,再也不需要来回挨家挨户跑了。这个大屏幕的出现就是select模型。


select模型最大的优点就是跨平台性好,估计也就只剩这个优点了。看看它的缺点,1. 首先支持的监控的描述符集合数量有限制,内核参数决定的。2. 每次select调用进程是阻塞的,并且需要把描述符集合从用户态copy到内核态,一旦数量增加,资源消耗线性增加。3. select返回的描述符是没有经过筛选的,上面有些有IO需求,有些没有。 还是采用上面的例子,尽管有了大屏幕,你还是得等,常常去看看大屏幕。其次大屏幕上对于那些没做好的小吃对我是没有用的,我只关注做好的,还得我人工筛选,有点笨。总之,一旦我买的东西很多很多,就会很麻烦。


更新的多路复用技术epoll出现了,几乎没有最大描述符数量限制,epoll实现很复杂,每个描述符注册到内核里,只需要一次,每个socket都是非阻塞,对于每个描述符都设计了callback,而且epoll返回的都是可用的描述符集合,不需要再扫描一遍寻找可IO的描述符。epoll还支持两种触发模型,epoll水平触发和边缘触发


同步和异步


再说点同步和异步的区别,这两者主要是针对发起者而言,是指做发出指令要做一件事后是否撒手不管,计算机上看是IO系统调用后本身实现的一个逻辑而已。 对于同步,就是发出指令要去做一件事,为了知道结果,所以可能一直去等待,或者不断去轮询是否做好。而异步则是发出指令要做一件事,不管结果直接返回去做其它事情,然后等待通知,这就算是异步。在计算机上看是针对进程发出IO请求后与内核交互的过程。同步就是IO发出后进程还一直在关注IO是否完成,要么阻塞,要么轮询等待结果。而异步是用户进程发出IO请求后立马返回去做其它事情,直到内核等待数据并且把数据准备好通知进程算是异步。重点就是在通知上!对于同步才会有阻塞或者非阻塞的概念,对于异步,没什么阻塞和非阻塞了。


继续上面的例子,去小吃街吃面,如果是同步机制,点了面之后我就要么一直等待着(同步阻塞),等待师傅把面做好,或者我时不时一会儿跑过去问下师傅面做好了没(同步非阻塞)。等到师傅面做好了,我再把面端回桌子(这个过程由我自己完成,也算阻塞)。 如果是异步机制,我在点面的时候留了一个手机号码,然后告诉师傅我坐几号桌,然后就可以安心去逛街了,等到师傅把面做好了,师傅会自己把面端到我指定的桌子上,然后发短信告诉我面已经按照我的要求准备好了,所以异步的效率肯定是最好的。


总结一下:

1、同步阻塞:等待数据是否准备好:阻塞, 数据从内核copy到用户空间:阻塞

2、同步非阻塞:等待数据是否准备好:非阻塞,但是需要很多空轮询; 数据从内核copy到用户空间:阻塞

3、select/poll:等待数据是否准备好:非阻塞,多路复用减少了不必要的空轮询; 数据从内核copy到用户空间:阻塞

4、异步:通知机制

Linux5大IO模型

阅读数 182

Linux的IO模型

阅读数 77

没有更多推荐了,返回首页