什么是协程?
协程:是单线程下实现并发,又称微线程,纤程。英文名Coroutine。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方(线程调度时候寄存器上下文及栈等保存在内存中),在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。
子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:
def A():
print( "1")
print("2")
print("3")
def B():
print("x")
print("y")
print("z")
A()
B()
输出结果:
1
2
3
x
y
z
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果就可能是:
1
x
2
y
3
z
但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。
看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行。
一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
需要强调的是:
1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(强调:不是io操作的切换与效率无关)
协程的优点
优点1: 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因 此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
优点2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中 控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。 因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利 用多核,又充分发挥协程的高效率,可获得极高的性能。
yield的简单实现
"""
@Time :2020/2/13 16:11
@Coding :UTF-8
@Auth :Bing.
@IDE :PyCharm
@File :18.协程—yield实现.py
@Motto:Move On
"""
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):
r=con1.__next__()
r=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("翠花")
con2=consumer("王大锤")
p=producer("大厨")
greenlet模块
Greenlet是python的一个C扩展,来源于Stackless python,旨在提供可自行调度的‘微线程’, 即协程。generator实现的协程在yield value时只能将value返回给调用者(caller)。 而在greenlet中,target.switch(value)可以切换到指定的协程(target), 然后yield value。greenlet用switch来表示协程的切换,从一个协程切换到另一个协程需要显式指定。是一种手动切换,后面会介绍能实现自动切换的Gevent
安装greenlet
pip install greenlet
greenlet实例
"""
@Time :2020/2/13 16:39
@Coding :UTF-8
@Auth :Bing.
@IDE :PyCharm
@File :19.协程—greenlet.py
@Motto:Move On
"""
from greenlet import greenlet
def work1():
print(12)
# 3.切换到test2()函数中
gr2.switch()
print(45)
gr2.switch()
def work2():
print(89)
gr1.switch()
print(43)
# 1.将要执行的函数封装到greenlet对象中
gr1=greenlet(work1)
gr2=greenlet(work2)
# 2.想先执行那个函数就可以使用 对象.swith()方法进行执行
gr1.switch()
gevent模块
gevent是第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
安装gevent
pip install gevent
gevent实例
"""
@Time :2020/2/13 17:08
@Coding :UTF-8
@Auth :Bing.
@IDE :PyCharm
@File :20.协程—gevent.py
@Motto:Move On
"""
import time
import requests
import gevent
def f(url):
print("get",url)
resp=requests.get(url)
data=resp.text
print("%d bytes received from %s"%(len(data),url))
# 1.普通模式
s = time.time()
f("http://langlang2017.com/img/banner1.png")
f("http://langlang2017.com/img/banner2.png")
f("http://langlang2017.com/img/banner3.png")
f("http://langlang2017.com/img/banner4.png")
e = time.time()
print("普通模式时间:",e-s)
# 2.使用gevet模块
start = time.time()
gevent.joinall(
[gevent.spawn(f,"http://langlang2017.com/img/banner1.png"), # 创建一个普通的greenlet对象并切换
gevent.spawn(f,"http://langlang2017.com/img/banner2.png"), # 创建一个普通的greenlet对象并切换
gevent.spawn(f,"http://langlang2017.com/img/banner3.png"), # 创建一个普通的greenlet对象并切换
gevent.spawn(f,"http://langlang2017.com/img/banner4.png"),] # 创建一个普通的greenlet对象并切换
)
print("gevent时间:",time.time()-start)