Python3 --- yield实现异步 + yield结合装饰器

一、yield实现异步

yield在python中初学时,觉得比较难理解。yield的作用: 
①返回一个值、②接收调用者的参数

分析下面的代码:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

def consumer():
    r = ''
    while True:
        n = yield r
        print("[Consumer] n = %d" %n)
        if not n:
            return
        print("[Consumer] consuming %s..." %n)
        r = '200 OK'

def produce(c):
    c.send(None)
    h = 0
    while h < 5:
        h = h + 1
        print("[Producer] producing %d..." %h)
        s = c.send(h)
        print("[Producer] consumer return: %s" %s)
    c.close()

c = consumer() #创建一个生成器
produce(c) #在该函数中,调用生成器的send()方法

程序运行截图

结合程序运行过程,可分析出:

第一步:在produce(c)函数中,调用了c.send(None)启动了生成器,遇到yield暂停;接着执行produce()中接下来的代码,从运行结果看,确实打印出了[Produce] producing 1 … 当程序运行至c.send(h)时,调用生成器并且通过yield传递了参数(h = 1)进入consumer()函数执行。

第二步,yield传递参数(h=1)给consumer()函数中的n,并接着上一次暂停处往下继续执行,打印出[Consumer] n = 1,[Consumer] consuming 1… ;在consumer()函数中此时 r 被赋值为’200 OK’,接着循环遇到yield, consumer()函数又暂停并且返回变量 r 的值,此时程序又进入produce(c)函数中接着执行。

第三步,produce(c)函数接着第一步中c.send(h)处,继续往下执行打印出[Producer] consumer return: 200 OK,并进行循环,打印[Producer] producing 2… 后,又调用c.send(h) 。。。如此循环回到第一步!

转载自:https://blog.csdn.net/u012730382/article/details/54135426

二、yield + 装饰器实现

import time
import _thread

def gen_coroutine(f):
    def wrapper(*args, **kwargs):
        gen_f = f()  # gen_f为生成器req_a
        r = next(gen_f)  # r为生成器long_io
        def fun(g):
            ret =next(g) # 执行生成器long_io
            try:
                gen_f.send(ret) # 将结果返回给req_a并使其继续执行
            except StopIteration:
                pass
        _thread.start_new_thread(fun, (r,))
    return wrapper

def long_io():
    print("开始执行IO操作")
    time.sleep(5)
    print("完成IO操作,yield回操作结果")
    yield "io result"

@gen_coroutine
def req_a():
    print("开始处理请求req_a")
    ret = yield long_io()
    print("ret: %s" % ret)
    print("完成处理请求req_a")

def req_b():
    print("开始处理请求req_b")
    time.sleep(2)
    print("完成处理请求req_b")

def main():
    req_a()
    req_b()
    while 1:
        pass

if __name__ == '__main__':
    main()

三、yield结合装饰器(无返回值)

def deco(func): 
    def wrapper():
        yie = func()
        print(type(yie))
        ret = next(yie)
        return yie
    return wrapper
@deco
def foo():
    while True:
        x = yield
        print("x-->",x)

g = foo()
g.send(1)
g.send(2)

 输出如下:

<class 'generator'>
x--> 1
x--> 2

以上代码的大概意思是: 
@deco是装饰器的语法糖模式,foo()是执行deco函数中wrapper函数,yie = func()是返回一个generator生成器,然后next(yie),这里原因是g.send不能用在第一个触发生成器 ,然后在main线程里面,g = foo()返回一个生成器,然后g.send(1) g.send(2)就输出foo()函数的打印输出

四、yield结合装饰器(有返回值)

我们在来看一个yield带返回值的

print("--------------------")
def deco(func):
    def wrapper():
        res = func()
        next(res)
        return res
    return wrapper
@deco
def foo():
    food_list = []
    while True:
        food = yield food_list  #返回添加food的列表
        food_list.append(food)
        print("elements in foodlist are:",food)
g = foo()
print(g.send('苹果'))
print(g.send('香蕉'))
re = g.send('菠萝')

输出如下:

E:\python\python_sdk\python.exe E:/python/py_pro/python.py
--------------------
elements in foodlist are: 苹果
['苹果']
elements in foodlist are: 香蕉
['苹果', '香蕉']
elements in foodlist are: 菠萝

Process finished with exit code 0

以上的代码g.send(‘xx”)返回一个food_list,然后输出列表信息即可

五、yield实现并发效果

以上的yield是跟装饰器结合使用,我们来看看yield能不能实现,程序遇到io实现切换? 
以下的例子是生产者生产数据,消费者消费数据


#基于yield并发执行
import time
def consumer():
    '''任务1:接收数据,处理数据'''
    while True:
        x=yield
        print("consumer拿到数据了x:",x)

def producer():
    '''任务2:生产数据'''
    g=consumer()
    next(g)
    for i in range(10):
        print("producer生产数据了",i)
        g.send(i)

start=time.time()
#基于yield保存状态,实现两个任务直接来回切换,即并发的效果
#PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
producer()

stop=time.time()
print(stop-start)

代码输出:

E:\python\python_sdk\python.exe E:/python/py_pro/python.py
producer生产数据了 0
consumer拿到数据了x: 0
producer生产数据了 1
consumer拿到数据了x: 1
producer生产数据了 2
consumer拿到数据了x: 2
producer生产数据了 3
consumer拿到数据了x: 3
producer生产数据了 4
consumer拿到数据了x: 4
producer生产数据了 5
consumer拿到数据了x: 5
producer生产数据了 6
consumer拿到数据了x: 6
producer生产数据了 7
consumer拿到数据了x: 7
producer生产数据了 8
consumer拿到数据了x: 8
producer生产数据了 9
consumer拿到数据了x: 9
0.0

Process finished with exit code 0

基于yield保存状态,实现两个任务直接来回切换,即并发的效果 
PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.

六、yield不能实现io切换

yield是实现了并发的效果,但是它能实现,在io阻塞,进行任务间切换么?我们来看一个例子 

import time
def consumer():
    '''任务1:接收数据,处理数据'''
    while True:
        x=yield
        print("consumer拿到数据了x:", x)

def producer():
    '''任务2:生产数据'''
    g=consumer()
    next(g)
    for i in range(3):
        print("producer生产数据了")
        g.send(i)
        time.sleep(2)

start=time.time()
producer() #并发执行,但是任务producer遇到io就会阻塞住,并不会切到该线程内的其他任务去执行

stop=time.time()
print(stop-start)

输出结果:

producer生产数据了
consumer拿到数据了x: 0
(有io阻塞)
producer生产数据了
consumer拿到数据了x: 1
(有io阻塞)
producer生产数据了
consumer拿到数据了x: 2

6.001317262649536

通过输出效果显示,并不能

七、greenlet任务切换

如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦(需要先得到初始化一次的生成器,然后再调用send。。。非常麻烦),而使用greenlet模块可以非常简单地实现这20个任务直接的切换

greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

from greenlet import greenlet

def eat(name):
    print('%s eat 1' %name)
    g2.switch('aaa')
    print('%s eat 2' %name)
    g2.switch()
def play(name):
    print('%s play 1' %name)
    g1.switch()
    print('%s play 2' %name)

g1=greenlet(eat)
g2=greenlet(play)

g1.switch('safly')#可以在第一次switch时传入参数,以后都不需要

输出如下:

E:\python\python_sdk\python.exe E:/python/py_pro/python.py
safly eat 1
aaa play 1
safly eat 2
aaa play 2

Process finished with exit code 0

转载自:https://blog.csdn.net/u013210620/article/details/78740042

猜你喜欢

转载自blog.csdn.net/Ka_Ka314/article/details/81507424