从同步异步、阻塞非阻塞到5种IO模型

同步异步、阻塞非阻塞

同步与异步

同步与异步在不同的场景下有不同的概念,在IO模型中的同步异步,主要区别在当任务A调用任务B的过程中,进程A是否继续进行。
如果A等待B的结果,则为同步
如果A不等待B的结果,则为异步

  • 同步状态下任务A的执行时依赖于任务B的,任务A成功是依赖于成功B的。而异步模式下两者是不相关的。
  • 异步的实现方式大概有三种:状态、通知和回调
    状态就是任务A去查询任务B的结果如何
    通知就是等任务B执行完成之后通知任务A来实现
    回调就是任务A定义个回调函数,当任务B结束后会自动调用回调函数
    我个人认为通知和回调的主要区别在于谁占有主动,以及调用的方式不同。

阻塞与非阻塞

阻塞和非阻塞的主要区别在,任务A等待B的结果的过程中,任务A是否会被挂起?
如果A等待的过程中不会挂起,则为阻塞
如果A不等待的过程中不会挂起,则为非阻塞

  • 同步阻塞的情况下,任务A会挂起,同步非阻塞的情况下任务A并不挂起。不挂起的情况下任务A保留有响应信号的能力。
  • 非阻塞的情况下并不会导致线程切换(只是不强制进行线程切换,如果该线程的时间片用完还是会切换的),可能效率更高,cpu利用率也更高,但是cpu可能会无意义空转,这样又会导致性能降低,所以使用何种方式需要看当前的系统情况。

概念之间的区别

上面两点似乎被分的很清楚,但是实际上这两个概念我认为指的是同一件事情,站的角度不同而已,过分的强调概念是无意义的。同步和异步更多的是两个任务之间数据通信方式,而阻塞非阻塞,则是站在当前线程自身的角度考虑是否可以在保留进程不挂起而继续进行任务来看的。

5种IO模型

用户空间和内核空间

要了解IO模型先要理解一linux类的系统下计算机的IO基本概念,5种IO模型实际上指的都是网络编程中的IO,数据从网络读取后先会被放入内核区,而后从内核区传入用户区。
5种IO模型中前四种全部都是同步IO只有异步IO,这里的同步和异步区别在于同步IO会在数据到达内核区的等待过程中进行各种方式的检查,如果查到了有数据到达了内核区则进行阻塞,因此整个IO的流程是分成2个阶段的。而异步IO的模式是只有数据完成了从内核区到用户区的复制之后才有通知。
而同步IO中根据第一阶段的不同策略又分成很多的不同模型。

5种IO模型

阻塞式IO

应用进程在调用recvfrom,后阻塞直至数据到达用户区之后才恢复。从应用进程的角度上说这是很合理而高效的,从性能的角度来说可以见上文中的讨论,首先他会引起线程切换,其次其cpu利用率低。
阻塞式IO

非阻塞式IO

应用进程在调用recvfrom的情况下并阻塞,而是返回一个为准备好的返回值,这时候应用进程可以继续运行,处理一些其他事情。通过这种轮询的方式查询是否有数据到达内核区,如果内核区有数据则需要阻塞应用线程,等待数据读取至用户区之后进行处理。
非阻塞IO看起来很傻,不停的循环,但是这样有两个好处,第一个是不会强制进行线程的切换,线程切换的代价是很大的,其次在两次查询直接可以用来做一些其他的事情,用户线程保有一定的响应能力。
非阻塞式IO

IO复用模型

非阻塞式IO中说到了采用轮询的方式查看是否有数据到达内核区,单线程的来看这个问题其实很傻,但是网络IO通常不是单个线程的。会有很多线程同时进行IO读写,因此我们可以依次检测多个IO读写任务,如果有某个任务所指定的数据到达则返回这样效率就高得多了,这就是所谓的IO复用。
其实这是网络模型中最为常用的模式,平时所谓的select、poll和epoll都是IO复用模型,只是细节上略有区别,具体的区别在后面讨论。
读取的两个阶段有2次调用2次返回,在第一个阶段是很多IO读取任务共用的,因此效率还是比较高的。
O复用模型

信号驱动IO
信号驱动IO我并不是很了解,目前的理解是linux类的程序下允许注册一个信号处理函数,我们可以用这种方式来处理IO,首先注册一个信号处理函数,然后应用程序继续进行,当有数据到达内核区后,会有一个信号返回给当前应用进程,当前引用进程进入信号处理过程,信号处理过程中会将数据拷贝至用户区的过程依然是阻塞的。
其实这里其实看出来,所谓的阻塞、非阻塞和同步、异步,其实是一件事情,只是在不同的语境下有些细微的区别而已,信号驱动IO的第一个阶段实际上就是典型的异步通知过程。但是因为其第二个阶段需要阻塞,因此整个信号驱动IO被归类为同步IO。
信号驱动IO

异步IO
异步IO的模型其实就是两个阶段均为异步的方式,用户进程异步调用函数后,检测数据的信号,但是用户线程自身并不挂起,而是继续运行,当数据完成了2个阶段的过程,读取至用户区之后才会通知应用程序。
这种方式和阻塞式IO的区别仅仅在于用户线程是否在两个阶段均阻塞。
异步IO

模型之间的讨论
其实对于典型的服务器环境,基本上都是默认采用IO复用模型的,这一点从模型自身的特性就可以看出。网络IO中即使采用非阻塞的方式其实本身也没有太大的意义,因为没有数据到来,那处理程序也没有其他的事情要做。而且从编程的角度上来说,IO复用这种同步的编程模式也更利于理解,不会使得程序显得很混乱。
关于效率问题,在上一章的讨论中以及讨论过了并不存在非阻塞效率更高的说法,只是在不同场景下有各自优势而且,但是在绝大多数场景下依然是IO复用更高效。

select、poll、epoll

select

首先select只有一个函数,创建、注册等待都是一次完成的。
特性
首先将fd_set从用户空间拷贝到内核区,然后注册回调函数 __pollwait。
回调函数的主要工作就是把当前线程挂到设备的等待队列中,不同的设备有不同的等待队列,如果该设备有个响应的响应(比如网络中读取的IO)则会唤醒该设备等待队列上的进程,___pollwait方法会返回一个描述读写操作是否就行的mask掩码,根据这个掩码给fd_set赋值。
如果遍历fd_set都没有一个可读写的mask掩码,这调用会 schedule_timeout将select线程进入睡眠,如果设备驱动自身资源可读或者,超时一定时间限都没人唤醒,则会唤醒当代队列上的线程重新遍历fd_set判断有没有就绪的fd。
缺点
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 ,尤其是在连接数量很多,但是活跃连接并不多的情况下。
- select支持的文件描述符数量太小了,默认是1024(64位系统是2048)

poll

poll和select基本上类似,区别是采用链表的形式去组织数据,所以没有数量限制。但是前两个问题依然存在

epoll

epoll的实现和上面两者有很大的不同。

epoll分为三个函数,分别表示创建,注册和阻塞三个情况。

这样在注册时会将epoll的句柄从用户区拷贝到内核区,所以只有一次复制,而不是每次等待都要复制,不存在第一个问题。
epoll为每个fd指定一个回调函数,当设备唤醒的时候就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表,epoll_wait要做的工作其实就是定期查看这个链表有没有就绪的fd就好,所以不需要遍历fd,不存在第二个问题。而且由于不需要遍历也不存在低活跃连接数量下效率低下的问题。
epoll没有fd上限,一般1G内存可以有10w个连接,与内存大小相关,而且可以修改。不存在第三个问题。

此外还有另一种说法,epoll采用mmap内存映射技术将内核区与用户区映射为同一片地方以减少系统复制代价,不清楚具体用在哪里。

epoll和select、poll之间最主要的区别还是前者基于回调机制进行响应,而select和poll基于系统调用,让内核遍历所有fd进行查询是否有设备就绪。

epoll本身还分为电平触发和边缘触发
电平触发(条件触发)LT模式:就是当fd就绪后进行通知,如果此次通知后没有操作响应,则下次依然通知,可用在阻塞模式也可以用在非阻塞模式。
边缘触发ET模式:就是当fd就绪后进行通知,但是如果此次没有响应操作,则下次不会通知。但只可以用在非阻塞模式下。
通常来说边缘触发效率更高,因为可以减少重复epoll的次数。

java视角的IO模型

BIO
所谓的BIO就是所谓的流,其实现就是阻塞式IO的模式效率本身是很低的。

NIO的非select模式
JAVA NIO中的不用select的情况下,其channel的非阻塞模型是非阻塞式IO。

NIO的select模式
JAVA NIO中的select实现是多路复用IO。底层使用的是poll或epoll。根据不同系统略有不同。
jdk实现(oraclejdk和openjdk)在linux环境下一般是水平触发的epol(linux kernels>2.6)或者是poll
如果想使用边缘出发的epoll可以使用netty,而且暴露了更多参数。

AIO
JAVA NIO2种的AIO则是异步IO模型。

Windows上用的是IOCP是典型的异步IO模型。
linux下并没有真正意义上的AIO。
netty的5.X版本中似乎希望实现真正的AIO但是这个版本被废弃了。
AIO用处实际上并不大远不如IO复用模型成熟有效。


参考资料:

java aio 编程

java 和netty epoll实现

怎样理解阻塞非阻塞与同步异步的区别?

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值