美文网首页
Python协程、yield、yield from

Python协程、yield、yield from

作者: imkobedroid | 来源:发表于2020-04-16 12:02 被阅读0次

    前言

    协程,又称微线程,纤程。英文名Coroutine。最近几年才在某些语言(如Lua)中得到广泛应用!

    协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

    1. 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
    2. 第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
    3. 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

    generator是什么?

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    创建generator有两种方式:
    1. 只要把一个列表生成式的[]改成(),就创建了一个generator
    2. 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

    python中定义协程

    Python对协程的支持是通过generator实现的。在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。

    yield

    类似于java中的return,返回一个值,但是不同点在于下次运行函数的时候是从yield后面的代码开始运行的,因为yield关键字标记了一个函数为generator,所以又一个next方法进行迭代,碰到yield后就不执行了并返回这个yield后面的值例如:

    def hello():
        print('hello world')
        yield '我暂停,并返回了'
        print('我继续执行了')
        yield '我又暂停了,并返回了'
    

    执行:

    h = hello()
    print(next(h))
    

    得到结果:

    hello world
    我暂停,并返回了
    

    再执行:

    h = hello()
    print(next(h))
    print(next(h))
    

    得到结果:

    hello world
    我暂停,并返回了
    我继续执行了
    我又暂停了,并返回了
    

    yield from

    yield from是yield的升级版

    def generator_1(title1):
        yield title1
    
    
    def generator_2(title):
        yield from title
    
    
    titles = ['python', 'java', 'c++']
    
    for title in generator_1(titles):
        print('生成器1:', title)
    
    for title in generator_2(titles):
        print('生成器2:', title)
        
        
        
    生成器1: ['python', 'java', 'c++']
    生成器2: python
    生成器2: java
    生成器2: c++
    

    可以看出yield from是将对象一个一个的迭代出来的,如果我们将上面yield的例子修改成yield from可以看到一样的结果:

    def hello():
        print('hello world')
        yield from '我暂停,并返回了'
        print('我继续执行了')
        yield from '我又暂停了,并返回了'
    
    
    h = hello()
    print(next(h))
    print(next(h))
    print(next(h))
    
    
    
    hello world
    我
    暂
    停
    

    因为是一个一个出来的,所以可以推断处yield from 后面跟的是需要迭代的对象!所以:

    yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器

    Python使用协程解决异步IO

    asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

    asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

    讲异步io前,我们需要了解一个模型:

    委托模型

    def gen():
        """子生成器"""
        yield 1
    
    
    def gen1(gen):
        """委托生成器"""
        yield from gen
    
    
    def main():
        """调用方"""
        g = gen()
        g1 = gen1(g)
        next(g1)  # 预刺激生成器
        g1.send(None)  # 启动生成器
    

    就是调用方将任务委托给一个中间委托生成器,委托生成器将任务派发给子生成器去完成的模型!
    所以最简单的任务执行模式:

    @asyncio.coroutine
    def hello():
        print('hello world')
        yield from asyncio.sleep(1)
        print('hello again')
    
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(hello())
    loop.close()
    
    
    hello world
    #间隔了一秒
    hello again
    

    我们用asyncio.sleep(1)实现线程休眠一秒模拟任务执行

    我们再来模拟最简单的异步任务执行:

    @asyncio.coroutine
    def hello():
        print('Hello world! (%s)' % threading.currentThread())
        yield from asyncio.sleep(1)
        print('Hello again! (%s)' % threading.currentThread())
    
    
    task = [hello(), hello()]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(task))
    loop.close()
    
    
    
    Hello world! (<_MainThread(MainThread, started 4508042688)>)
    Hello world! (<_MainThread(MainThread, started 4508042688)>)
    #中间间隔一秒
    Hello again! (<_MainThread(MainThread, started 4508042688)>)
    Hello again! (<_MainThread(MainThread, started 4508042688)>)
    

    可以看到我们两个任务都在一个线程内完成的,这也是协程的特点,而且没有阻塞异步完成的!

    我们再将子生成器模拟出来形成真正的任务异步:

    final_result = {}
    
    
    # 子生成器
    def salesNum(key):
        total = 0
        nums = []
    
        while True:  # 使用while循环不断的从调用方接收值
            x = yield
            print(key, "- 销量统计:%s" % x)
            if not x:
                break
            total += x
            nums.append(x)
        return total, nums
    
    
    # 委托生成器
    
    def sales(key):
        while True:
            final_result[key] = yield from salesNum(key)
            print(key + '销量统计完成')
    
    
    def perform():
        data_set = {
            '牙膏': [100, 200, 300],
            '衣服': [400, 500, 600],
            '鞋子': [700, 800, 900]
        }
        for key, data in data_set.items():
            print('start key:', key)
            s = sales(key)
            next(s)
            for i in data:
                s.send(i)
            s.send(None)
        print('final_result:', final_result)
    
    
    perform()
    

    结果是:

    start key: 牙膏
    牙膏 - 销量统计:100
    牙膏 - 销量统计:200
    牙膏 - 销量统计:300
    牙膏 - 销量统计:None
    牙膏销量统计完成
    start key: 衣服
    衣服 - 销量统计:400
    衣服 - 销量统计:500
    衣服 - 销量统计:600
    衣服 - 销量统计:None
    衣服销量统计完成
    start key: 鞋子
    鞋子 - 销量统计:700
    鞋子 - 销量统计:800
    鞋子 - 销量统计:900
    鞋子 - 销量统计:None
    鞋子销量统计完成
    final_result: {'牙膏': (600, [100, 200, 300]), '衣服': (1500, [400, 500, 600]), '鞋子': (2400, [700, 800, 900])}
    

    这就是协程异步的操作

    async/await简化异步IO

    从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。

    请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

    1. 把@asyncio.coroutine替换为async
    2. 把yield from替换为await

    例子:

    async def hello():
        print('hello world (%s)' % threading.currentThread())
        await asyncio.sleep(1)
        print('hello again (%s)' % threading.currentThread())
    
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(hello())
    loop.close()
    
    
    hello world (<_MainThread(MainThread, started 4464768448)>)
    hello again (<_MainThread(MainThread, started 4464768448)>)
    

    总结

    asyncio提供了完善的异步IO支持;异步操作需要在coroutine中通过yield from完成;多个coroutine可以封装成一组Task然后并发执行。协程并发与执行效率非常的高效,现在kotlin也支持协程并且优化很好了,所以建议采用协程执行异步操作!

    相关文章

      网友评论

          本文标题:Python协程、yield、yield from

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