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也支持协程并且优化很好了,所以建议采用协程执行异步操作!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容