协程到底有什么用?6种I/O模式告诉你!
The following article is from 码农的荒岛求生 Author 陆小风
点击关注公众号,一周多次包邮送书
大家好,我是小风哥,今天来聊一聊协程的作用。
假设磁盘上有10个文件,你需要读取的内存,那么你该怎么用代码实现呢?
最简单的方法——串行
这可能是大多数同学都能想到的最简单方法,那就是一个一个的读取,读完一个接着读下一个。用代码表示是这样的:for file in files:
result = file.read()
process(result)
代码简单,容易理解
可维护性好,这代码交给谁都能维护的了(论程序员的核心竞争力在哪里)
稍好的方法,并行
那么,该怎么并行读取文件呢?显然,地球人都知道,线程就是用来并行的。我们可以同时开启10个线程,每个线程中读取一个文件。用代码实现就是这样的:def read_and_process(file):
result = file.read()
process(result)
def main():
files = [fileA,fileB,fileC......]
for file in files:
create_thread(read_and_process,
file).run()
# 等待这些线程执行完成
创建线程需要消耗系统资源,像内存等(想一想为什么?)
调度开销,尤其是当线程数量较多且都比较繁忙时(同样想一想为什么?)
创建多个线程不一定能加快I/O(如果此时设备处理能力已经饱和)
事件驱动 + 异步
没错,即使在单个线程中,使用事件驱动+异步也可以实现IO并行处理,Node.js就是非常典型的例子。为什么单线程也可以做到并行呢?这是基于这样两个事实:相对于CPU的处理速度来说,IO是非常慢的
IO不怎么需要计算资源
event_loop = EventLoop()
def add_to_event_loop(event_loop, file):
file.asyn_read() # 文件异步读取
event_loop.add(file)
while event_loop:
file = event_loop.wait_one_IO_ready()
process(file.result)
def add_to_event_loop(event_loop, file):
file.asyn_read() # 文件异步读取
event_loop.add(file)
def main():
files = [fileA,fileB,fileC ...]
event_loop = EventLoop()
for file in files:
add_to_event_loop(event_loop, file)
while event_loop:
file = event_loop.wait_one_IO_ready()
process(file.result)
多线程 VS 单线程 + event loop
接下来我们看下程序执行的效果。在多线程情况下,假设有10个文件,每个文件读取需要1秒,那么很简单,并行读取10个文件需要1秒。那么对于单线程+event loop呢?我们再次看下event loop + 异步版本的代码:def add_to_event_loop(event_loop, file):
file.asyn_read() # 文件异步读取
event_loop.add(file)
def main():
files = [fileA,fileB,fileC......]
event_loop = EventLoop()
for file in files:
add_to_event_loop(event_loop, file)
while event_loop:
file = event_loop.wait_one_IO_ready()
process(file.result)
file = event_loop.wait_one_IO_ready()
for file in files: add_to_event_loop(event_loop, file)
因此在event_loop.wait_one_IO_ready等待的1s期间,剩下的9个IO也完成了,也就是说event_loop.wait_one_IO_ready函数只是在第一次循环时会等待1s,但是此后的9次循环会直接返回,原因就在于剩下的9个IO也完成了。因此整个程序的执行耗时也是1秒。是不是很神奇,我们只用一个线程就达到了10个线程的效果。这就是event loop + 异步的威力所在。一个好听的名字:Reactors模式
本质上,我们上述给出的event loop简单代码片段做的事情本质上和生物一样:给出刺激,做出反应。我们这里的给出event,然后处理event。这本质上就是所谓的Reactors模式。现在你应该明白所谓的Reactors模式是怎么一回事了吧。所谓的一些看上去复杂的异步框架其核心不过就是这里给出的代码片段,只是这些框架可以支持更加复杂的多阶段任务处理以及各种类型的IO。而我们这里给出的代码片段只能处理文件读取这一类IO。把回调也加进来
如果我们需要处理各种类型的IO上述代码片段会有什么问题吗?问题就在于上述代码片段就不会这么简单了,针对不同类型会有不同的处理方法,因此上述process方法需要判断IO类型然后有针对性的处理,这会使得代码越来越复杂,越来越难以维护。幸好我们也有应对策略,这就是回调。关于回调函数,请参考这篇《程序员应如何理解回调函数》。我们可以把IO完成后的处理任务封装到回调函数中,然后和IO一并注册到event loop。就像这样:def IO_type_1(event_loop, io):
io.start()
def callback(result):
process_IO_type_1(result)
event_loop.add((io, callback))
while event_loop:
io, callback = event_loop.wait_one_IO_ready()
callback(io.result)
回调函数的问题
虽然回调函数使得event loop内部更加简洁,但依然有其它问题,让我们来仔细看看回调函数:def start_IO_type_1(event_loop, io):
io.start()
def callback(result):
process_IO_type_1(result)
event_loop.add((io, callback))
发起IO
IO处理
问题出在哪里
让我们再来仔细的看看问题出在了哪里?同步编程模式下很简单,但是同步模式下发起IO,线程会被阻塞,这样我们就不得不创建多个线程,但是创建过多线程又会有性能问题。这样为了发起IO后不阻塞当前线程我们就不得不采用异步编程+event loop。在这种模式下,异步发起IO不会阻塞调用线程,我们可以使用单线程加异步编程的方法来实现多线程效果,但是在这种模式下处理一个IO的流程又不得不被拆分成两部分,这样的代码违反程序员直觉,因此难以维护。那么很自然的,有没有一种方法既能有同步编程的简单理解又会有异步编程的非阻塞呢?答案是肯定的,这就是协程。关于协程请参考《程序员应如何理解协程》。Finally!终于到了协程
利用协程我可以以同步的形式来异步编程。这是什么意思呢?我们之所以采用异步编程是为了发起IO后不阻塞当前线程,而是用协程,程序员可以自行决定在什么时刻挂起当前协程,这样也不会阻塞当前线程。而协程最棒的一点就在于挂起后可以暂存执行状态,恢复运行后可以在挂起点继续运行,这样我们就不再需要像回调那样将一个IO的处理流程拆分成两部分了。因此我们可以在发起异步IO,这样不会阻塞当前线程,同时在发起异步IO后挂起当前协程,当IO完成后恢复该协程的运行,这样我们就可以实现同步的方式来异步编程了。接下来我们就用协程来改造一下回调版本的IO处理方式:def start_IO_type_1(io):
io.start() # IO异步请求
yield # 暂停当前协程
process_IO_type_1(result) # 处理返回结果
def add_to_event_loop(io, event_loop):
coroutine = start_IO_type_1(io)
next(coroutine)
event_loop.add(coroutine)
while event_loop:
coroutine = event_loop.wait_one_IO_ready()
next(coroutine)
总结
看上去简简单单的IO实际上一点都不简单吧。为了高效进行IO操作,我们采用的技术是这样演进的:单线程串行 + 阻塞式IO(同步)
多线程并行 + 阻塞式IO(并行)
单线程 + 非阻塞式IO(异步) + event loop
单线程 + 非阻塞式IO(异步) + event loop + 回调
Reactor模式(更好的单线程 + 非阻塞式IO+ event loop + 回调)
单线程 + 非阻塞式IO(异步) + event loop + 协程
推荐阅读
• Elasticsearch 写入优化,从 3000 到 8000/s,让你的 ES 飞起来!• 4种 Redis 集群方案介绍+优缺点对比• mysql插入数据会失败?为什么?• Spring Boot 整合多数据源,这才叫优雅~• Spring Boot 一个接口同时支持 form 表单、form-data、json 的优雅写法• 要说网络的 TCP 协议,这 28 张图,我服了
👇更多内容请点击👇