美文网首页
再探协程和线程的比较——实例

再探协程和线程的比较——实例

作者: 东方胖 | 来源:发表于2022-05-06 11:42 被阅读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 一起同步运作

    相关文章

      网友评论

          本文标题:再探协程和线程的比较——实例

          本文链接:https://www.haomeiwen.com/subject/eawayrtx.html