0%

同步、异步、阻塞、非阻塞

同步、异步、阻塞、非阻塞

在网络编程、乃至分布式系统开发中经常会遇到这四个概念,实际上四个概念是很难讲清楚关系与区别的,可以有多种解读的方式,这里只是对网上的资料进行整理,以网络IO为实例来尝试理解这种情况下的四个概念、以及记录下*nix/Windows下的几种网络IO模型。 #### 同步与异步

同步和异步关注的是通信机制。

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,但是一旦调用返回,就得到返回值了。而异步则是在操作完成时会收到一个通知(消息、回调等)。

阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

(在处理网络IO时)四者的关系

在处理(网络) IO 的时候,阻塞和非阻塞都是同步 IO。只有使用了特殊的 API 才是异步 IO。

 sync-async-blocking-non-blocking

在这里阻塞与非阻塞的区分在于网络IO时进行IO操作的线程会不会挂起。其实对于某些IO模型来说,阻塞非阻塞其实比较难定义,比如select模型中指用户线程不会阻塞于send recv等网络IO操作上,但在select操作本身上是阻塞的,不过因为网络IO并未阻塞的原因仍称selec模型为非阻塞的模型。

而此时同步与异步的区分在于数据如何从系统缓冲区(内核)到达用户缓冲区(用户空间)。例如在windows下,一个IO读操作可以分为两大步:1、从IO设备读取数据,保存在系统的缓冲区;2、从系统缓冲区拷贝到用户的缓冲区。如果一个读操作的两个步骤都不在用户线程中执行,那么这个读操作就是异步的;只要有一个步骤在用户线程中执行,这个读操作就是同步的。

类UNIX下5种可用的网络I/O模型

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

前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:(在recvform时)需要等待数据从内核复制到应用缓冲区(用户空间)。相反,异步I/O只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。

阻塞式I/O模型

默认情况下,所有套接字都是阻塞的。一个输入操作通常包括两个不同阶段:(1)等待数据准备好;(2)从内核向进程复制数据。

非阻塞式I/O

 进程把一个套接字设置成非阻塞是在通知内核,当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误。

#### I/O多路复用

虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。

信号驱动式I/O

用的很少,就不做讲解了

异步I/O

这类函数的工作机制是告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到用户空间)完成后通知我们。

I/O多路复用中的select、poll、epoll的区别

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里都能够支持,其中epoll是Linux所特有,而select则是POSIX所规定,一般操作系统(包括Windows--Windows也兼容POSIX的较老标准)均有实现。

select:select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。单个进程可监视的fd数量被限制、对socket进行扫描时是采用轮询的方法导致效率较低。

poll:poll本质上和select没有区别,但没有最大连接数的限制因为它是基于链表来存储的。

epoll: epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。因为epoll没有最大并发连接的限制、只有活跃可用的FD才会调用callback函数使得效率提升、使用mmap内存映射减少复制开销。

Windows下的七种网络IO模型

  • 阻塞模型;
  • 非阻塞模型;
  • Select模型;
  • WSAAsynSelect模型;
  • WSAEventSelect模型;
  • Overlapped IO(重叠IO)模型;
  • Completion Port(完成端口、IOCP)模型;

前五种IO模型都是同步IO操作,后两者都使用了重叠结构(Overlapped)所以是真正的异步IO。Overlapped IO模型(基于事件通知的重叠I/O和基于完成例程的重叠I/O),如果不是特别必要就不要去使用了,因为不仅使用和理解起来也不算简单而且还有性能上的瓶颈,不如直接使用完成端口。

阻塞模型

Socket默认的就是阻塞模式,然后使用send、recv时都会阻塞直到写/读完毕。

非阻塞模型

调用ioctlsocket函数设置Socket的FIONBIO为1就转为非阻塞模式。当recv和send函数没有准备好数据时,函数不会阻塞,立即返回错误值,用GetLastError返回的错误码为WSAEWOULDBLOCK。

Select模型

Select模型是非阻塞的,函数内部自动检测WSAEWOULDBLOCK状态,还能有超时设定。对read,write,except三种事件进行分别检测,except指带外数据可读取,read和write的定义是广义的,accept,close等消息也纳入到read。

其实select的原理就是对Socket集合进行扫描,有事件或者超时则退出,所以select的效率也是和Socket数量成线性关系,而且需要我们自己循环检查哪个Socket有事件发生。

WSAAsynSelect模型

WSAAsynSelect是Windows特有的,可以在一个套接字上接收以Windows消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函数自动将套接字设置(转变)为非阻塞模式,并向Windows注册一个或多个网络事件lEvent,并提供一个通知时使用的窗口句柄hWnd。当注册的事件发生时,对应的窗口将收到一个基于消息的通知wMsg。MFC中的CSocket也采用了这个模型。

它的缺点就是,每个sock事件处理需要一个窗口句柄,如果sock很多的情况下,资源和性能可想而知了。

WSAEventSelect模型

WSAEventSelect模型类似WSAAsynSelect模型,但最主要的区别是网络事件发生时会被发送到一个Event对象句柄,而不是发送到一个窗口。

Overlapped IO模型

该模型是以Windows的重叠IO机制为基础,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来完成Overlapped IO。

有两个方法可以接收到重叠IO请求的完成情况通知:事件对象通知(event object notification)、完成例程(completion routines)。完成例程(Completion Routine)是使用APC(Asynchronous Procedure Calls)异步回调函数来实现,大致流程和事件通知模型差不多,只不过WSARecv注册时,加上了lpCompletionRoutine参数。

Completion Port模型

完成端口是一种Windows内核对象。完成端口用于异步方式的重叠I/O。简单地,可以把完成端口看成系统维护的一个队列,操作系统把重叠IO操作完成的事件通知放到该队列里,由于是暴露 “操作完成”的事件通知,所以命名为“完成端口”(Completion Ports)。即完成端口可以简单地看做是操作系统在维护一个线程池进行重叠IO。

参考资料: