18.1 线程与协程对比
首先,分析spinner_thread.py脚本。
# -*- coding:utf-8 -*-
import threading
import itertools
import time
import sys
class Signal:#这个类定义一个简单的可变对象:其中有个go属性,用于从外部控制线程
go = True
def spin(msg, signal):#这个函数会在单独的线程内运行。signal参数是前面定义的Signal类的实例
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):#这其实是个无限循环,因为itertools.cycle函数会在指定的序列中反复不断地生成元素
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))#这是显示文本式动画的诀窍所在:使用退格符('\x08')把光标移回来
time.sleep(.1)
if not signal.go:
break
write(' '*len(status) + '\x08'*len(status))#使用空格清楚状态信息,把光标移回开头
def slow_function():#假设这是耗时的计算
#假装等待I/O一段时间
time.sleep(3)#调用sleep函数阻塞会主线程,不过一定要这么做,以便释放GIL,创建从属线程
return 42
def supervisor():#这个函数设置从属线程,显示线程对象,运行耗时的计算,最后杀死线程
signal = Signal()
spinner = threading.Thread(target=spin,args=("thinking!",signal))
print('spinner object:', spinner)#显示从属线程对象
spinner.start()#启动从属线程
result = slow_function()#运行slow_function函数,阻塞主线程。同时从属线程以动画形式显示旋转指针
signal.go = False#改变signal的状态;这会终止spin函数中的for循环
spinner.join()#等待spinner线程结束
return result
def main():
result = supervisor()#运行supervisor函数
print('Answer:', result)
if __name__=='__main__':
main()
使用@asyncio.coroutine装饰器替代线程,实现相同的行为。
# -*- coding:utf-8 -*-
import asyncio
import itertools
import sys
@asyncio.coroutine#打算交给asyncio的线程要使用@asyncio.coroutine装饰,不是必须,但强烈建议
def spin(msg):#不需要signal参数
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
try:
yield from asyncio.sleep(.1)#代替time.sleep(.1),这样的休眠不会阻塞事件循环
except asyncio.CancelledError:#如果spin函数苏醒后抛出该异常,其原因是发出了取消请求,因此退出循环
break
write(' ' * len(status) + '\x08' * len(status))
@asyncio.coroutine
def slow_function():#slow_function是协程,在用休眠假装进行I/O操作时,使用yield from继续执行事件循环。
# 假装等待I/O一段时间
yield from asyncio.sleep(3)#把控制权交给主循环,在休眠结束后恢复这个协程
return 42
@asyncio.coroutine#打算
def supervisor():
spinner = asyncio.async(spin('thinking!'))
print('spinner object:', spinner)
result = yield from slow_function()#驱动slow_function函数。结束后,获取返回值。同时事件继续运行,因为slow_funcion函数最后使用yield from asyncio.sleep(3)把控制权交给了主循环
spinner.cancel()
return result
def main():
loop = asyncio.get_event_loop()#获取事件循环的引用
result = loop.run_until_complete(supervisor())#驱动supervisor协程;这个协程的返回值是这次调用的返回值
loop.close()
print('Answer:', result)
if __name__ == '__main__':
main()