在linux下编程,我们常见有四种调试模式,分别是同步(Sync),异步(Async),阻塞(block)和非阻塞(Unblock).
同步和异步的概念描述的是用户的线程与内核IO操作的方式:同步是指用户线程发起IO请求后需要等待内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后任然继续不断的执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
linux下编程有五种IO模型,下面分别来介绍一下:
(1)同步阻塞IO(Block IO):
就是传统的IO模型,在linux中默认情况下的socket进程都是阻塞模式。当用户进程调用了read()这个系统调用,内核就开始了IO的的第一个阶段:准备数据。对于网络IO来说,很多时候数据在一开始还没有到达(比喻还没收到一个完整的udp数据包),内核就要等待足够多的数据到来。在用户进程这边,整个进程会被阻塞。当内核得到足够多的数据时,内核才会将准备好的数据发给用户进程,然后内核返回结果,用户进程解除阻塞。像socket的其他函数如write,listen等也都是阻塞型的,其改进的方法就是使用多进程或者多线程,这样其中一个连接阻塞也不会影响其他的连接。
(2)同步非阻塞(Non-blocking IO):
同步非阻塞IO是建立在同步阻塞IO的基础上的,将socket设置为NONBLOCK,这个可以使用ioctl()系统调用设置。这样用户进程可以在发起IO请求后立即返回,如果该次请求没有读取到任何数据,用户线程需要不断的发起IO请求,知道数据到达后才真正的读取到数据,继续执行。整个IO请求的过程中,虽然每次发起的IO请求后可以立即返回,但是为了等到数据,仍需要不断的重复请求,消耗了大量的CPU资源。所以一般很少会用这种IO模型。
(3)IO多路复用(IO Multiplexing):
IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,我在之前写过一篇博客就是关于实现多路复用的select函数,它可以避免同步阻塞IO模型中的轮询等待问题,还有poll,epoll也是这种模型。在该模式下,用户需要将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回,当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。从流程上看,使用select函数进行IO请求和同步阻塞相似,甚至还添加监视socket,以及调用select函数的额外操作,效率更差。使用select函数的最大优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断的调用设了select读取被激活的socket,可达到在同一个线程内同时处理多个IO请求的目的,而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
(4) 信号驱动IO(signal driven IO):
调用sigaltion系统调用,当内核中IO数据就绪时以SIGIO信号通知请求进程,请求进程
再把数据从内核读入到用户空间,这一步是阻塞的。
(5)异步IO(Asynchronous IO):
即经典的Proactor设计模式,也称为异步非阻塞IO。“真正”的异步IO需要操作系统更强
的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异
步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户
线程直接使用即可。
相比于IO多路复用模型,信号驱动IO和异步IO并不十分常用
为了更好的了解,我把前三种IO模型做了个形象的比喻:
1.阻塞IO:给朋友发一条微信,说晚上一起打球,并等待朋友的回信,这期间你会一直等,不会做其他事情,一直到他给你回复。
2.非阻塞IO:给朋友发微信,如果不回,一直发,这期间你只会发短信,而不会干其他事情,直到他给你回复。
3.IO多路复用:给朋友发完微信,叫一位舍友帮忙监视手机是否收到回复,这期间你可以干其他的事,你可以玩游戏,也可以看书,而这位舍友的名字可以叫select,也可以叫poll,也可以叫epll。