再探协程和线程的比较——实例
2022-05-06 本文已影响0人
东方胖
之前对比过在 IO 发生了上下文切换时线程和协程的对比
本文继续探讨几个细节。
例子来自 《流畅的Python》一书
例子是在控制台刷出一个旋转的 ‘/’ 字符
线程的写法:
- 创建一个任务,实现在控制台每隔 0.1 秒打印一个 符号 ‘|/-'中的一个,当然是循环打印,以给视觉留下一个旋转针的感觉。
- 主线程假设是在做其它的事情
import itertools
import threading
import time
import sys
class Signal:
go = True
def spin(msg, signal):
write = sys.stdout.write
flush = sys.stdout.flush
for char in itertools.cycle("|/-\\"):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
time.sleep(0.1)
if not signal.go:
break
write(' ' * len(status) + '\x08' * len(status))
def slow_function():
time.sleep(3)
return 42
def supervisor():
signal = Signal()
spinner = threading.Thread(target=spin, args=('doing', signal))
print("spin object:", spinner)
spinner.start()
result = slow_function()
signal.go = False
spinner.join()
return result
def main():
result = supervisor()
print("Answer:", result)
if __name__ == '__main__':
main()
- itertools 包有一个现成的循环遍历的 cycle 工具,这个是一个迭代器
- Signal 类里面其实没有什么内容,作为一个名字空间的作用,维护一个控制线程终止的遍历 go,我们应该也可以不使用一个类,直接声明一个全局的变量,但是不够优雅,不是一个好习惯
- supervisor 这是一个权力高于 spin 任务的管理类,它做的事情是创建线程,开启,终止,关闭之类的动作,还负责其它任务如 slow_function 这种调度
这个框架可以用来处理那些需要让用户等待,但耗时任务时不那么无聊的慢任务,已经我把它改成一个装饰器,放到我的代码库里
我们说,IO任务,尽量使用异步协程来处理
它比线程的好处有很多,
第一个是,代码不需要另辟蹊径取一个线程,同时要考虑一些锁,临界区,以及线程管理的问题。当然协程也是有一些代价的,它在低版本 Python 支持的不是那么好,同时,像 yield, yield from 装饰器 ayncio.coroutine 经历了一些变化,到现在 Python3.10 的 async await语法,对用户来说,有一些困惑。
- yield 最初在 Python 2.4 以前的版本,只是作为生成器声明的手段,起一个让出 CPU 控制权的作用,但是它不能从外部向内部传递数据
- 于是 Python2.5 丰富了 yield 的功能,使得它不仅可以 yield 生成一个值出去,还可以通过在左边放置一个变量接收数据,从而在一个最小集合里支持了协程的基本语义
- Python3.3 增加了几乎是 Python 语言中最难索解的yield from 语法 和 asyncio包
- Python3.7 引进了 aysnc 和 await 语法。await 几乎是 yield from 的取代
- Python3.8 禁用了 aysnc.coroutine 的装饰器声明协程的方式,正式鼓励用户使用 async await, 协程的语义趋于稳定
到 Python3.8 后,基本上,我们可以用 async await 来写协程。
上面的例子用协程的写法如下:
import asyncio
import sys
import itertools
async def spin(msg):
write = sys.stdout.write
flush = sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
try:
await asyncio.sleep(0.05)
except asyncio.CancelledError:
break
write(' ' * len(status) + '\x08' * len(status))
async def slow_function():
await asyncio.sleep(3)
return 42
async def supervisor():
spinner = asyncio.create_task(spin('thiking'))
print('spinner object: ', spinner)
result = await slow_function()
spinner.cancel()
return result
def main():
loop = asyncio.get_event_loop()
result = loop.run_until_complete(supervisor())
loop.close()
print("Answer:", result)
if __name__ == '__main__':
main()
- spin函数在线程写法中,它是作为线程的target任务,改成协程之后,这自然就对应了一个协程
- IO aysnio.sleep 等待处 声明为await ,代表此处会让出控制权
- 搭档任务 slow_function() 也必须是一个协程。在第一种写法里,slow_function 在主线程 和supervisor 一起同步运作