协程

什么是协程

协程:协助程序,线程和进程都是抢占式特点,线程和进程的切换我们是不能参与的。
而协程是非抢占式特点,协程也存在着切换,这种切换是由我们用户来控制的。
协程主解决的是IO的操作。

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

协程的形象理解

协程的原理很简单,打个比方就能讲明白了:假设说有十个人去食堂打饭,这个食堂比较穷,只有一个打饭的窗口,并且也只有一个打饭阿姨,那么打饭就只能一个一个排队来打咯。这十个人胃口很大,每个人都要点5个菜,但这十个人又有个毛病就是做事情都犹豫不决,所以点菜的时候就会站在那里,每点一个菜后都会想下一个菜点啥,因此后面的人等的很着急呀。这样一直站着也不是个事情吧,所以打菜的阿姨看到某个人犹豫5秒后就开始吼一声,会让他排到队伍最后去,先让别人打菜,等轮到他的时候他也差不多想好吃啥了。这确实是个不错的方法,但也有一个缺点,那就是打菜的阿姨会等每个人5秒钟,如果那个人在5秒内没有做出决定吃啥,其实这5秒就是浪费了。一个人点一个菜就是浪费5秒,十个人每个人点5个菜可就浪费的多啦(菜都凉了要)。那咋办呢?
这个时候阿姨发话了:大家都是学生,学生就要自觉,我以后也不主动让你们排到最后去了,如果你们觉得自己会犹豫不决,就自己主动点直接点一个菜就站后面去,等下次排到的时候也差不多想好吃啥了。这个方法果然有效,大家点了菜后想的第一件事情不是下一个菜吃啥,而是自己会不会犹豫,如果会犹豫那直接排到队伍后面去,如果不会的话就直接接着点菜就行了。这样一来整个队伍没有任何时间是浪费的,效率自然就高了。

这个例子里的排队阿姨的那声吼就是我们的CPU中断,用于切换上下文。每个打饭的学生就是一个task。而每个人自己决定自己要不要让出窗口的这种行为,其实就是我们协程的核心思想。

在用线程的时候,其实虽然CPU把时间给了你,你也不一定有活干,比如你要等IO、等信号啥的,这些时间CPU给了你你也没用呀。

在用协程的时候,CPU就不来分配时间了,时间由你们自己决定,你觉得干这件事情很耗时,要等IO啥的,你就干一会歇一会,等到等IO的时候就主动让出CPU,让别人上去干活,别人也是讲道理的,干一会也会把时间让给你。协程就是使用了这种思想,让编程者控制各个任务的运行顺序,从而最大可能的发挥CPU的性能。

这个例子里的排队阿姨的那声吼就是我们的CPU中断,用于切换上下文。每个打饭的学生就是一个task。而每个人自己决定自己要不要让出窗口的这种行为,其实就是我们协程的核心思想。

在用线程的时候,其实虽然CPU把时间给了你,你也不一定有活干,比如你要等IO、等信号啥的,这些时间CPU给了你你也没用呀。

在用协程的时候,CPU就不来分配时间了,时间由你们自己决定,你觉得干这件事情很耗时,要等IO啥的,你就干一会歇一会,等到等IO的时候就主动让出CPU,让别人上去干活,别人也是讲道理的,干一会也会把时间让给你。协程就是使用了这种思想,让编程者控制各个任务的运行顺序,从而最大可能的发挥CPU的性能。

协程的优点

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

优势一:最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

优势二:就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

使用yield实现协程

import time
# 定义一个消费者
def consumer(name):
    '''
    这是一个生成器,调用的时候才执行
    :param name: 消费者的名字
    :return: 返回的是包子
    '''
    print('消费者准备吃包子------------')
    while True:
        new_baozi = yield  #接收调用生成器的值
        print('%s吃第%d个包子'%(name,new_baozi))
        time.sleep(1) # 休息一秒
# 定义生产者
def producer(name):
    con1.__next__()
    con2.__next__()
    count = 1
    while True:
        print('%s正在生产%d个包子和第%d个包子'%(name,count,count+1))
        # 调用生成器并且发送数据
        con1.send(count)
        con2.send(count+1)
        count+=2

if __name__ == '__main__':
    con1 = consumer('翠花') # 消费者1
    con2 = consumer('王大锤') # 消费者2
    p = producer('大厨')

在这里插入图片描述

为什么yield可以实现协程

在Python中,协程通过yield实现。因为当一个函数中有yield存在的时候,这个函数是生成器,那么当你调用这个函数的时候,你在函数体中写的代码并没有被执行,而是只返回了一个生成器对象,这个需要特别注意。然后,你的代码将会在每次使用这个生成器的时候被执行。

前面讲过yield表达式的两个关键作用:①返回一个值、②接收调用者的参数

“调用者”与“被调用者”之间的通信是通过send()进行联系的

正是因为yield实现的生成器具备“中断等待的功能”,才使得yield可以实现协程。

greenlet 模块

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之
间随意切换,而不需把这个函数先声明为generator

from greenlet import greenlet

def work1():
    print(12)
    # 3.切换到test2()中执行
    gr2.switch()
    print(34)
    # 又一次切换到test2()中
    gr2.switch()
def work2():
    print(56)
    gr1.switch()
    print(78)
# 1、将要执行的函数封装到greenlet对象中
gr1 = greenlet(work1)
gr2 = greenlet(work2)
# 2、想先执行那个函数就可以 对象.switch()方法进行执行
gr1.switch()

在这里插入图片描述

gevent模块

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
gevent是第三方库,通过 greenlet 实现 coroutine,创建、调度的开销比 线程(thread) 还小,因此程序内部的 执行流 效率高。

gevent 实现了 python 标准库中一些阻塞库的非阻塞版本,如 socket、os、select 等 (全部的可参考 gevent1.0 的 monkey.py 源码),可用这些非阻塞的库替代 python 标准库中的阻塞的库。

gevent 提供的 API 与 python 标准库中的用法和名称类似。

其基本思想是:当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

gevent特点

基于libev的快速事件循环(Linux上epoll,FreeBSD上kqueue)。
基于greenlet的轻量级执行单元。
API的概念和Python标准库一致(如事件,队列)。
可以配合socket,ssl模块使用。
能够使用标准库和第三方模块创建标准的阻塞套接字(gevent.monkey)。
默认通过线程池进行DNS查询,也可通过c-are(通过GEVENT_RESOLVER=ares环境变量开启)。
TCP/UDP/HTTP服务器
子进程支持(通过gevent.subprocess)
线程池

gevent常用方法:

gevent.spawn()创建一个普通的Greenlet对象并切换
gevent.spawn_later(seconds=3)延时创建一个普通的Greenlet对象并切换
gevent.spawn_raw()创建的协程对象属于一个组
gevent.getcurrent()返回当前正在执行的greenlet
gevent.joinall(jobs)将协程任务添加到事件循环,接收一个任务列表
gevent.wait()可以替代join函数等待循环结束,也可以传入协程对象列表
gevent.kill()杀死一个协程
gevent.killall()杀死一个协程列表里的所有协程
monkey.patch_all()非常重要,会自动将python的一些标准模块替换成gevent框架
import time
import requests
import gevent
# 使用gevent模块和普通下载进行对比
def f(url):
    print('GET:%s'%url)
    resp = requests.get(url)
    data = resp.text
    print('%d bytes received from %s'%(len(data),url))

# 1.普通模式下载
s = time.time()
f('http://www.langlang2017.com./img/banner1.png')
f('http://www.langlang2017.com./img/banner2.png')
f('http://www.langlang2017.com./img/banner3.png')
f('http://www.langlang2017.com./img/banner4.png')
e = time.time()
print('普通模式时间',e-s)

# 2、使用gevent模块
srart = time.time()
gevent.joinall(
[gevent.spawn(f,'http://www.langlang2017.com./img/banner1.png') ,# 创建一个普通的greenlent对象并切换
gevent.spawn(f,'http://www.langlang2017.com./img/banner2.png'),
gevent.spawn(f,'http://www.langlang2017.com./img/banner3.png'),
gevent.spawn(f,'http://www.langlang2017.com./img/banner4.png')]
)
print('gevent时间',time.time()-srart)
'''
如果下载量小的话,普通模式(串行)的下载可能会比,gevent花费的时间少
如果下载的数据量大的话,gevent性能才能显示出来

'''

gevent.spawn() 创建一个普通的Greenlet对象并切换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值