IO模型

IO模型

服务器编程中常用的4种IO模型:

  1. 同步阻塞IO(Blocking IO)
  2. 同步非阻塞IO(non-blocking IO)
  3. IO多路复用(IO Mutiplexing)
  4. 异步IO(Asynchronous IO)

同步和异步的概念描述的是用户线程与内核的交互方式:

  • 同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;
  • 异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:

  • 阻塞是指IO操作需要彻底完成后才返回到用户空间;
  • 非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

同步阻塞IO

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

mark

如图,用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。

用户线程使用同步阻塞IO模型的伪代码描述为:

read(socket, buffer);
process(buffer);

即用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。

优点:

  • 最快返回数据,无延迟
  • 简单易实现

缺点:

  • CPU利用率极低

用户空间与内核空间

  • 对于32位操作系统,它的虚存为4G,
  • 为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚存划分为两部分,一部分为内核空间,一部分为用户空间。
  • 对于linux系统,0~3G地址为用户空间,3G-4G地址为内核空间。
  • IO的读写都是系统调用,因此当我们read一个socket时,是将数据读到内核空间的buffer,然后再将buffer拷贝到用户空间(copy_to_user)

同步非阻塞IO

同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK。这样做用户线程可以在发起IO请求后可以立即返回。

mark

如图所示,由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。

用户线程使用同步非阻塞IO模型的伪代码描述为:

while(read(socket, buffer) != SUCCESS)
    ;
process(buffer);

即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。

优点:

  • 比起同步阻塞,可以在等待任务的时间内做其他事(也就是“后台”可以有多个任务同时进行)

缺点:

  • 任务完成的响应延迟增大
  • 不断轮询造成CPU利用率低

IO多路复用

IO多路复用模型是建立在内核提供的多路分离函数select基础之上的。

mark

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

从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

用户线程使用select函数的伪代码描述为:

select(socket);
while(1)
{
    sockets = select();
    for(socket in sockets)
    {
        if(can_read(socket)) {
            read(socket, buffer);
    process(buffer);
    }
}

优点:

  • 用户可以在一个线程内同时处理多个socket的IO请求,比起多线程的同步阻塞模型,减少了线程切换的开销。

缺点:

  • 增加了select函数的开销,对单个socket连接没有优势

多线程的成本

  • 更复杂的设计:
    • 多线程直接的交互
    • 共享内存
    • 线程同步的错误难以检测,复现和修复
  • 上下文切换开销
    • 本地数据,程序指针等
  • 加重资源消耗
    • 线程本地堆栈

异步IO

在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

参考网站:
https://github.com/rainzhaojy/blogs/issues
http://www.cnblogs.com/fanzhidongyzby/p/4098546.html
https://www.jianshu.com/p/486b0965c296

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值