咱不咬文嚼字地掰什么是Blocking I/O,什么是Async I/O,没啥意思。代码是用来解决问题的。有的时候透彻理解问题,会比透彻理解某个具体的解法更重要。
I/O阻塞的问题来源就是,当我们需要输入输出的时候,特别是通过网络传输数据的时候。从请求发出,到得到对方的应答确认是一段时间的。比如说代码中调用
sock.connect()
实际对应的TCP包有三个
client = syn => server
client <= syn+ack = server
client = ack => server
这一来两去的不得好几百ms么?在这段时间内,执行I/O操作的程序其实是被阻塞住了不能去干别的事情的。如何让机器在等待I/O的时候同时干别的事情从而充分利用资源就是我们要解决的问题。
流程阻塞的问题与I/O阻塞的问题类似,比如
stop_server()
migrate_db_to_newer_version()
start_server()
我们有一个三步骤的流程,第一步把服务器停掉,可能要花1分钟,第二步升级数据库,可能要花30分钟,第三步在升级之后把服务器重新启动起来,可能需要30秒。这样的一个流程在执行的时候,对于执行者来说也是被阻塞了的。为了充分利用资源,同样希望执行上述脚本的机器能够在流程等待的时候同时去干点的事情。甚至如下面的流程
if is_approved_by_admin():
stop_server()
migrate_db_to_newer_version()
start_server()
如果流程中需要人工审批,我们甚至希望执行该流程机器可以彻底把资源释放,把流程状态存到数据库里,等用户审批确认的时候再把流程继续执行。
当然除了能够在等待的时候更充分地利用资源之外,流程这个领域往往还有更高的要求。比如很多情况下我们希望能够在阻塞状态下,清楚地知道每个流程的当前状态,甚至是对状态进行一些人工干预。经典的工作流的需求有三种建模方法,一种是我们这里使用的串行process的方式,另外一种是FSM(有限状态机),另外一种是petri net。所以我们这里谈的流程阻塞只是一种简化的流程建模的实现。
在某种意义下,I/O阻塞和流程阻塞都是阻塞问题。对于编码实现去解决阻塞问题的码农来说,就是如何用代码告诉计算机如何处理阻塞:
- 在阻塞的时候同时去干点什么别的事情
- 阻塞前后的代码逻辑如何连贯起来
从实现的效果上来说两个评价标准
- 快不快:并发数,吞吐量,延迟
- 好不好懂:我认为阅读相关代码时候眼球移动距离可以用来度量Logic Locality。这个指标可以用来代表代码质量
在如何表达“阻塞如何处理”这个问题上,有太多经典模式了。比如多线程,selector,proactor,协程等。每种模式解决的问题是一样的,无非就是在快不快和好不好懂两个问题上做了不同的取舍而已。