阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

IO异步

207次阅读
没有评论

共计 3007 个字符,预计需要花费 8 分钟才能阅读完成。

一、说明

对于耗时的过程,我们将其交给别人(如其另外一个线程)去执行,而我们继续往下处理,当别人执行完耗时操作后再将结果反馈给我们,这就是我们所说的异步

二、回调写法实现原理

​“`python
import time
import threading

def long_io(callback):
“”“将耗时的操作交给另一线程来处理”””
def fun(cb): # 回调函数作为参数
“”“耗时操作”””
print(“开始执行 IO 操作”)
time.sleep(5)
print(“完成 IO 操作,并执行回调函数”)
cb(“io result”) # 执行回调函数
threading.Thread(target=fun, args=(callback,)).start() # 开启线程执行耗时操作

def on_finish(ret):
“”“回调函数”””
print(“开始执行回调函数 on_finish”)
print(“ret: %s”% ret)
print(“完成执行回调函数 on_finish”)

def req_a():
print(“开始处理请求 req_a”)
long_io(on_finish)
print(“离开处理请求 req_a”)

def req_b():
print(“开始处理请求 req_b”)
time.sleep(2) # 添加此句来突出显示程序执行的过程
print(“完成处理请求 req_b”)

def main():
req_a()
req_b()

if name ==‘main’:
main()


执行过程:

开始处理请求 req_a
开始执行 IO 操作
离开处理请求 req_a
开始处理请求 req_b
完成处理请求 req_b
完成 IO 操作,并执行回调函数
开始执行回调函数 on_finish
ret: io result
完成执行回调函数 on_finish


说明:异步的特点是程序存在多个步调,即本属于同一个过程的代码可能在不同的步调上同时执行

三、协程写法实现原理

  • 说明

    在使用回调函数写异步程序时,需将本属于一个执行逻辑(处理请求 a)的代码拆分成两个函数 req_a 和 on_finish,这与同步程序的写法相差很大。而同步程序更便于理解业务逻辑,所以我们能否用同步代码的写法来编写异步程序

  • 初始版本

    import time import threading gen = None # 全局生成器,供 long_io 使用 def long_io(): def fun(): print("开始执行 IO 操作") global gen time.sleep(5) try: print("完成 IO 操作,并 send 结果唤醒挂起程序继续执行") gen.send("io result") # 使用 send 返回结果并唤醒程序继续执行 except StopIteration: # 捕获生成器完成迭代,防止程序退出 pass threading.Thread(target=fun).start() def req_a(): print("开始处理请求 req_a") ret = yield long_io() print("ret: %s" % ret) print("完成处理请求 req_a") def req_b(): print("开始处理请求 req_b") time.sleep(2) print("完成处理请求 req_b") def main(): global gen gen = req_a() next(gen) # 开启生成器 req_a 的执行 req_b() if __name__ == '__main__': main()

执行过程:

  开始处理请求 req_a
  开始执行 IO 操作
  开始处理请求 req_b
  完成处理请求 req_b
  完成 IO 操作,并 send 结果唤醒挂起程序继续执行
  ret: io result
  完成处理请求 req_a
  • 升级版本

    说明:我们在上面编写出的版本虽然 req_a 的编写方式很类似与同步代码,但是在 main 中调用 req_a 的时候却不能将其简单的视为普通函数,而是需要作为生成器对待

    import time import threading gen = None # 全局生成器,供 long_io 使用 def gen_coroutine(f): def wrapper(*args, **kwargs): global gen gen = f() next(gen) return wrapper def long_io(): def fun(): print("开始执行 IO 操作") global gen time.sleep(5) try: print("完成 IO 操作,并 send 结果唤醒挂起程序继续执行") gen.send("io result") # 使用 send 返回结果并唤醒程序继续执行 except StopIteration: # 捕获生成器完成迭代,防止程序退出 pass threading.Thread(target=fun).start() @gen_coroutine def req_a(): print("开始处理请求 req_a") ret = yield long_io() print("ret: %s" % ret) print("完成处理请求 req_a") def req_b(): print("开始处理请求 req_b") time.sleep(2) print("完成处理请求 req_b") def main(): req_a() req_b() if __name__ == '__main__': main()

执行过程:

  开始处理请求 req_a
  开始执行 IO 操作
  开始处理请求 req_b
  完成处理请求 req_b
  完成 IO 操作,并 send 结果唤醒挂起程序继续执行
  ret: io result
  完成处理请求 req_a
  • 最终版本

    import time import threading def gen_coroutine(f): def wrapper(*args, **kwargs): gen_f = f() # gen_f 为生成器 req_a r = next(gen_f) # r 为生成器 long_io def fun(g): ret = next(g) # 执行生成器 long_io try: gen_f.send(ret) # 将结果返回给 req_a 并使其继续执行 except StopIteration: pass threading.Thread(target=fun, args=(r,)).start() return wrapper def long_io(): print("开始执行 IO 操作") time.sleep(5) print("完成 IO 操作,yield 回操作结果") yield "io result" @gen_coroutine def req_a(): print("开始处理请求 req_a") ret = yield long_io() print("ret: %s" % ret) print("完成处理请求 req_a") def req_b(): print("开始处理请求 req_b") time.sleep(2) print("完成处理请求 req_b") def main(): req_a() req_b() if __name__ == '__main__': main()

执行过程:

  开始处理请求 req_a
  开始执行 IO 操作
  开始处理请求 req_b
  完成处理请求 req_b
  完成 IO 操作,yield 回操作结果
  ret: io result
  完成处理请求 req_a

说明:这个最终版本就是理解 Tornado 异步编程原理的最简易模型,但是,Tornado 实现异步的机制不是线程,而是 epoll,即将异步过程交给 epoll 执行并进行监视回调

正文完
星哥说事-微信公众号
post-qrcode
 
星锅
版权声明:本站原创文章,由 星锅 2022-05-26发表,共计3007字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中