Python:yield协程

协程

协程,又称微线程,协程是一种用户态的轻量级线程(操作系统根本就不知道他的存在,是用户自己控制的)
协程是一种函数,特点是其处理过程可以在特定点挂起和恢复,因此典型情况下,协程将执行到某个特定的语句,之后执行过程被挂起等待某些数据,在这个挂起点上,程序是的其他部分可以继续执行,一旦数据到来,协程就从其他挂起点恢复执行,执行一些处理,并可能将其处理结果发送给另一个协程,协程可以有多个入口点和退出点,因为挂起和恢复执行的位置都不止一处。
在需要将多个函数应用与同样的数据时,或者是需要创建数据处理流水线时,或者是某个主数带有几个函数时,协程都是有用的,协程也可以作为线程的替代,并且更加简单,负载也更低。
在Python中,协程是一个从yield表达式中提取输入的函数,也可以将处理结果发送给接收者函数(该函数本身必须是一个协程)。

协程的好处与缺点

优点

无需线程上下文切换的开销
无需原子操作锁定以及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本。一个CPU支持上万的协程都不是问题

缺点

无法利用多核资源:协程本质是单线程,他不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上。
进行阻塞操作会阻塞掉整个程序。因此我们需要解决这个问题,才能进行大范围的运用。比如说如果在程序运行中遇到I/O操作就会被阻塞从而造成程序的阻塞。因此我们需要在他阻塞的时候切换到另一个函数。

例子

yield协作的例子

import time

def producer():
    "用于把生产的产品发生给客户"
    #做准备
    c1.next()
    c2.next()
    c3.next()
    n=0
    #给他们5个产品
    while n<5:
        n+=1
        time.sleep(2) #制作时间
        c1.send(n)
        c2.send(n)
        c3.send(n)
        print "\033[31;0mprodecer making baozi\033[1m"

    pass

def consumer(name):
    "客户接受生产者的产品"
    while True:
        print "recieve。。。"
        new=yield
        print "%s revieve [%s]"%(name,new)



if __name__ == '__main__':
    #三个顾客
    c1=consumer('C1')
    c2=consumer('C2')
    c3=consumer('C3')
    producer()

Gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行

在gevent里面,上下文切换是通过yielding来完成的. 在下面的例子里, 我们有两个上下文,通过调用gevent.sleep(0),让程序阻塞,它们各自yield向对方

我们可以通过调试来看看程序的运行结果

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0) #这里会阻塞,然后跳到下一个协程函数
    print('Explicit context switch to foo again')

def bar():
    print('Explicit context to bar')
    gevent.sleep(0)#这里会阻塞,然后跳到下一个协程函数
    print('Implicit context switch back to bar')
#用异步的方式运行这些函数
gevent.joinall([
    gevent.spawn(foo),#生成
    gevent.spawn(bar),#生成
])

当我们在受限于网络或IO的函数中使用gevent,这些函数会被协作式的调度, gevent的真正能力会得到发挥。Gevent处理了所有的细节, 来保证你的网络库会在可能的时候,隐式交出greenlet上下文的执行权。 这样的一种用法是如何强大,怎么强调都不为过

同步与异步的性能区别


def task(pid):
    "一些不确定的任务"
    gevent.sleep(0.5)
    print("Task %s done"%pid)

def syn():
    #串行执行task函数
    for i in range(10):
        task(i)


def ayn():
    "异步执行函数"
    gevent_list=[gevent.spawn(task,i) for i in range(10)]
    gevent.joinall(gevent_list)
t1=time.time()
print("syn")
syn()
t2=time.time()
print "syn time is ",t2-t1

print('ayn')
ayn()

print "ayn time is ",time.time()-t2

结果:

syn
Task 0 done
Task 1 done
Task 2 done
Task 3 done
Task 4 done
Task 5 done
Task 6 done
Task 7 done
Task 8 done
Task 9 done
syn time is  5.01099991798
ayn
Task 0 done
Task 1 done
Task 2 done
Task 7 done
Task 6 done
Task 5 done
Task 4 done
Task 3 done
Task 8 done
Task 9 done
ayn time is  0.50200009346

上面的比较就已经很明显了
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组gevent_list中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。 

猴子补丁(Monkey patching)

猴子补丁(monkey patching) 尝试让gevent这个强大的协程模型变得生动有趣,但现在到了讨论猴子补丁的黑色艺术 的时候了。对于monkey.patch_socket()这个命令,这个 纯粹副作用命令是用来改变标准socket库的。
Python的运行环境允许我们在运行时修改大部分的对象,包括模块,类甚至函数。 这是个一般说来令人惊奇的坏主意,因为它创造了“隐式的副作用”,如果出现问题 它很多时候是极难调试的。虽然如此,在极端情况下当一个库需要修改Python本身 的基础行为的时候,猴子补丁就派上用场了。在这种情况下,gevent能够 修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。

例如,Redis的python绑定一般使用常规的tcp socket来与redis-server实例通信。 通过简单地调用gevent.monkey.patch_all(),可以使得redis的绑定协作式的调度 请求,与gevent栈的其它部分一起工作。

这让我们可以将一般不能与gevent共同工作的库结合起来,而不用写哪怕一行代码。 虽然猴子补丁仍然是邪恶的(evil),但在这种情况下它是“有用的邪恶(useful evil)”。

遇到IO阻塞时会自动切换任务 

利用猴子补丁来完成示例

from gevent import monkey
import gevent
import urllib

monkey.patch_all()
def get(url):
    print("Get:%s"%url)
    resp=urllib.urlopen(url)
    data=resp.read() #I/O就阻塞了,然后切换任务
    print("%d bytes recived from %s "%(len(data),url))

gevent.joinall(
    [
        gevent.spawn(get,'https://www.python.org/'),
        gevent.spawn(get,'https://www.yahoo.com/'),
        gevent.spawn(get,'https://github.com/')
    ]
)

通过gevent实现单线程下的多socket并发

import gevent
import sockeimport sys
import time
from gevent import  monkey
monkey.patch_all()

def server(port):
    s=socket.socket()
    s.bind(('0.0.0.0',port))
    s.listen(500)
    print("start listening")
    #这里就是socket多并发
    while True:
        client,addr=s.accept()
        print 'come from',addr
        gevent.spawn(handler,client) 

def handler(conn):
    try:
        while True:
            data=conn.recv(1024)
            # print('recv',data)
            conn.send(data)
            if not data:
                conn.shutdown(socket.SHUT_WR) #socket.SHUT_WR表示不允许继续发送数据了
    except Exception as e:
        # print e
        pass
    finally:
        conn.close()

if __name__ == '__main__':
    server(8001)

转载于:https://www.cnblogs.com/cmustard/p/6769921.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值