Python协程、yield、yield from

前言

协程,又称微线程,纤程。英文名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)