【注】:本篇侧重基本概念理解,不涉及代码实现细节
同步与异步
同步
同步是指程序在执行一次调用时,在没有得到结果之前,程序不会返回,一旦程序返回了,也就得到了返回结果。
异步
异步是指程序在发出调用时,不等调用结果返回就会立即返回,异步通常结合回调实现。(异步往往意味着非阻塞)
总结
同步是自己等结果,异步是别人通知你
阻塞与非阻塞
阻塞
阻塞是指在程序不能立即得到返回结果时(一个时间片以内)当前线程会被挂起。
非阻塞
非阻塞是指在程序调用不能立即得到返回结果时,当前线程不会被挂起。
总结
阻塞程序让出CPU被挂起,非阻塞程序还会继续占用CPU
阻塞与非阻塞 同步与异步的关系
同步阻塞
常见的IO操作,IO阻塞,程序被挂起,在没有得到结果之前之前不会返回。
同步非阻塞
正常编写的程序不涉及到大量耗时的操作都是同步非阻塞
异步阻塞
让程序异步,就是为了为了让其非阻塞,目前还未见过异步阻塞的程序
异步非阻塞
异步非阻塞模型常见的有两种。一种是以node.js为代表的单线程事件+回调的模型,另外一种是以python语言为代表的协程方式。利用协程实现的异步非阻塞本身也是基于事件+回调的模型,只不过此时回调函数利用协程实现后,各个协程可以独自管理自己的状态,避免了大量回调带来的回调地狱
常用的五种服务器模型
同步阻塞模型
- 最古老的服务器编程模型,效率低下,程序一旦遇到耗时IO便会被阻塞挂起
- 单进程单线程
多进程模型
- 多进程模型支持多道程序的并发执行,服务器的并发能力相比同步阻塞模型大大提高
- 说的并发对单核CPU而言,只是宏观上并行,在微观上仍然是串行
- 对于单个进程而言仍然是同步阻塞
- 进程是系统进行资源分配的最小单位,系统分配资源需要从用户态切换到内核态,这是一个耗时的过程,所以对于高并发的场景,基于多进程的服务器并扛不住。
多线程模型
- 线程是CPU进行调度的最小单位
- 多个线程共享进程资源
- 线程切换相比进程切换资源开销要小很多
- 对于单个线程而言仍然是同步阻塞的。
- 多线程模型不好的方面是遇到临界资源处理时需要锁机制,加锁会大大降低服务器的并发性能
注:上面三种模型均是同步阻塞的模型
IO多路复用(select/poll)
- IO多路复用的本质就是在单进程内将原来阻塞的IO的操作改为事件+回调的模式,向操作系统注册感兴趣的事件,当有事件发生时操作系统会返回发生事件的句柄,然后触发对应回调函数即可。
- select模型是早期Linux支持的IO多路复用模型,效率低下,select的最大文件描述符只支持1024个
- select返回整个文件句柄的数组,只有遍历数组才能发现哪些句柄发生了事件
epoll模型
- epoll模型是目前Linux支持IO多路复用最高效的模型,它是select模型的增强版
- 相比select模型,epoll模型消除了最大文件描述符不再有限制,也就意味着最多可以支持65535个
- epoll只返回有事件发生的句柄
- epoll模型的缺点是仍然没有解决异步+回调中的回调地狱问题
上面基本上把常用的五种IO模型给介绍完了,可以看到当服务器模型发展到今天,唯一困扰的问题就是异步IO中的无限回调带来的回调地狱和回调栈撕裂问题,于是目前很多语言开始支持协程,协程是比线程更小的执行单元,不同于进程线程,协程对于操作系统是透明的,它的切换可以由程序员自己显示指定,基于协程解决了回调函数之间的资源传递问题,各个协程独立管理自己的资源。epoll+Coroutine的模型就是Python中高性能web框架Tornado的实现原理。回头会专门整理一篇Tornado模型的实现细节(更侧重代码实现)。