文章目录
- 前言
- 1.python 生成器
- 1.1 python 生成器概述
- 1.2 关键字yield/yield from
- 1.3 next/send函数
- 1.4 StopInteration异常
- 1.5 利用生成器实现生产者-消费者模型
- 1.6 生成器和协程的关系
- 2.生成器协程调度器
- 3.python事件驱动编程
- 4.实现协程调度器
- 5.python 协程生态
前言
多进程和多线程在实际编程中用的已经非常多了,这篇文章的作用是记录下学习协程的心得体会,争取一篇文章搞定.
协程的好处不多说了,可以说是I/O密集型的利器.其实对于IO密集型任务我们还有一种选择就是协程。协程,又称微线程,英文名Coroutine,是运行在单线程中的“并发”,协程相比多线程的一大优势就是省去了多线程之间的切换开销,获得了更高的运行效率。Python中的异步IO模块asyncio就是基本的协程模块。
协程的切换不同于线程切换,是由程序自身控制的,没有切换的开销。协程不需要多线程的锁机制,因为都是在同一个线程中运行,所以没有同时访问数据的问题,执行效率比多线程高很多。
1.python 生成器
1.1 python 生成器概述
三个概念: 迭代/迭代器/可迭代对象
什么是生成器?
生成器也是一个可迭代对象
[num for num in range(10)]
(num for num in range(10) )
上面是一个列表,下面是一个生成器
生成器的特点
- 迭代完成一次,就指向最后一个了,再次迭代就迭代不出来了.和list不同.
- 占用内存大小不同
import sys
sys.getsizeof(10)
sys.getsizeof("Hello World")
sys.getsizeof(l)
sys.getsizeof(gen)
gen = (num for num in range(10000))
l = [num for num in range(10000)]
sys.getsizeof(l)
sys.getsizeof(gen)
生成器几乎所占的内存不发生变化,而列表则随着元素的增多而增大!
生成器和列表非常类似,拥有一样的迭代方式,内存模型上更节省内存空间,生成器只能遍历一次。可以实现延时计算,并且用生成器代码更简洁。
1.2 关键字yield/yield from
Php, js,python C++ 中都有这个关键字,是一个比较高级的语法现象。
yield 只能再函数里面使用
def func():
print("Hello World!");
type(func)输出 <class ‘function’>
def func():
print("Hello World!");
yield()
调用func()
注意 type(func()) 他返回了一个生成器!!!
普通函数 调用 返回None 是因为函数本来就返回none 就是普通函数的执行。
yield 就把一个函数变成了一个生成器。
def func():
for i in range(10):
print(i)
def func2():
for i in range(10):
yield i
for i in func2():
print(i)
同样生成了0到9的10个数字。
1.3 next/send函数
多进程和多线程体现的是操作系统的能力,而协程体现的是程序员的流程控制能力。看下面的例子,甲,乙两个工人模拟两个工作任务交替进行,在单线程内实现了类似多线程的功能。
import time
def task1():
while True:
yield "<甲>也累了,让<乙>工作一会儿"
time.sleep(1)
print("<甲>工作了一段时间.....")
def task2(t):
next(t)
while True:
print("-----------------------------------")
print("<乙>工作了一段时间.....")
time.sleep(2)
print("<乙>累了,让<甲>工作一会儿....")
ret = t.send(None)
print(ret)
t.close()
if __name__ == '__main__':
t = task1()
task2(t)
输出:
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
问题想一想为啥先执行乙的语句,再执行甲。
t = task1() 只是返回了一个生成器。task2(t) 的运行,next(t) 使得进入task1中执行,并且遇到yield停下,此时next(t)的返回值就是:<甲>也累了,让<乙>工作一会儿
然后进入task2的 while True, 直到遇到t.send(None), 执行权反转到task1,执行yield的下面一句。
这里的send可以不传none,
最早的时候,Python提供了yield关键字,用于制造生成器。也就是说,包含有yield的函数,都是一个生成器!
yield的语法规则是:在yield这里暂停函数的执行,并返回yield后面表达式的值(默认为None),直到被next()方法再次调用时,从上次暂停的yield代码处继续往下执行。
每个生成器都可以执行send()方法,为生成器内部的yield语句发送数据。此时yield语句不再只是yield xxxx的形式,还可以是var = yield xxxx的赋值形式。它同时具备两个功能,一是暂停并返回函数,二是接收外部send()方法发送过来的值,重新激活函数,并将这个值赋值给var变量!
def simple_coroutine():
print('-> 启动协程')
y = 10
x = yield y
print('-> 协程接收到了x的值:', x)
my_coro = simple_coroutine()
ret = next(my_coro)
print(ret)
my_coro.send(100)
-> 启动协程
10
-> 协程接收到了x的值: 1000
Traceback (most recent call last):
File "c:\Users\jianming_ge\Desktop\碳汇算法第一版\carbon_code\单线程模拟多线程.py", line 54, in <module>
my_coro.send(1000)
StopIteration
协程可以处于下面四个状态中的一个。当前状态可以导入inspect模块,使用inspect.getgeneratorstate(…) 方法查看,该方法会返回下述字符串中的一个。
‘GEN_CREATED’ 等待开始执行。
‘GEN_RUNNING’ 协程正在执行。
‘GEN_SUSPENDED’ 在yield表达式处暂停。
‘GEN_CLOSED’ 执行结束。
1.4 StopInteration异常
平时在进行python编程的时候,一般不关注异常,但学习生成器的时候,需要利用这个stopinteration进行编程