协程是一种比线程更轻量级的机制。
它可以使开发者暂停一个正在运行的函数,等到合适的时机再接着运行。
协程相对于线程的优势:
1. 调度时不需要上下文切换
2. 不需要线程锁,因为多个协程是可以运行在一个线程中的
为了理解Python协程,让我们先来了解Python中的迭代器和生成器。
在python中,可以用for ... in ... 格式访问的对象称为迭代器(iterables),比如list,string都是迭代器。
我们可以方便地一个一个读取迭代器中的数据:
-
data = [x*x for x in range(5)]
-
for i in data:
-
print(i)
但是,上面的做法会在执行时把迭代器中所有的数据读入内存。有时,当数据量很大时,我们可能并不想这么做。
这就引入了生成器(generators)。生成器是一种特殊的迭代器,它执行时不会把所有的迭代数据放入内存,而是保存获取数据的算法,和当前执行到的状态。
将上面例子的list改成生成器只需要把[]改成():
-
data = (x*x for x in range(5))
-
for i in data:
-
print(i)
data变成生成器后的一个“后果”是只能迭代一次,下一次迭代会取不到数据:
-
>>> data = (x*x for x in range(3))
-
>>> for i in data:
-
print(i)
-
0
-
1
-
4
-
>>> for i in data:
-
print(i)
-
注意(x*x for x in range(5))类型是generator而不是tuple,可以用type()看到
-
>>> type((x*x for x in range(5)))
-
<class 'generator'>
如果要生成tuple类型的话,需要转换一下:
-
>>> type(tuple(x*x for x in range(5)))
-
<class 'tuple'>
我们也可以使用yield把普通函数变成生成器。
-
>>> def fun():
-
for i in [x*x for x in range(5)]:
-
yield i
-
>>> type(fun())
-
<class 'generator'>
我们拿到生成器对象后,可以通过__next__()方法不断获取下一个值,当不再有新的值时,会抛出StopIteration异常。
-
>>> f = fun()
-
>>> f.__next__()
-
0
-
>>> f.__next__()
-
1
-
>>> f.__next__()
-
4
-
>>> f.__next__()
-
9
-
>>> f.__next__()
-
16
-
>>> f.__next__()
-
Traceback (most recent call last):
-
File "", line 1, in <module>
-
f.__next__()
-
StopIteration
另外也能使用生成器的send()方法的参数作为生成器内部yield的返回值,具体可参考。
协程举例
协程一种常见的使用场景是管道(pipeline)。即B要用到A的结果,C又要用到B的结果,其中A为IO操作。最简单的做法就是顺序执行,A拿到所有的数据后传给B,B全部处理完再传给C。但是,当数据量很大时这种方法显然是不明智的。
我们会想到创建3个线程A,B,C,A拿到一定数据后发送给B,B和C在没有数据时阻塞等待,接收到数据就执行。这种方法能很好地实现我们的目标,但在python中,有更好的方法。
来看代码:
-
import time
-
-
def A():
-
for i in range(5):
-
time.sleep(1)
-
print('[A] producing %d' % i)
-
yield i
-
-
-
def B(data):
-
for i in data:
-
print('[B] producing %d' % (i * i))
-
yield i * i
-
-
def C(data):
-
for i in data:
-
if i % 2:
-
print('[C] producing %d' % i)
-
-
-
if __name__=='__main__':
-
a = A()
-
b = B(a)
-
C(b)
上面例子中的函数A()模拟费时的IO操作,B()将A生成的数据取平方,C()中判断值是不是奇数,是的话打印出来。
执行结果:
-
>>>
-
[A] producing 0
-
[B] producing 0
-
[A] producing 1
-
[B] producing 1
-
[C] producing 1
-
[A] producing 2
-
[B] producing 4
-
[A] producing 3
-
[B] producing 9
-
[C] producing 9
-
[A] producing 4
-
[B] producing 16
要注意的是,当我们运行生成器函数的时候,其实函数并没有执行,而是返回一个生成器对象。对象被迭代时才会真正执行。
下面我们再实现一个简单的协程调度器。
-
import time
-
import queue
-
-
class Task(object):
-
taskid = 0
-
def __init__(self, fun, args):
-
self.taskid = Task.taskid
-
Task.taskid += 1
-
# 获取task的生成器
-
self.instance = fun(args)
-
-
def run(self):
-
# 执行下一个迭代
-
self.instance.__next__()
-
-
class Scheduler(object):
-
def __init__(self):
-
self.tasks = {}
-
# 任务队列
-
self.task_queue = queue.Queue()
-
-
def create_task(self, name, fun, args):
-
task = Task(fun, args)
-
self.tasks[name] = task
-
self.task_queue.put(task)
-
-
def loop(self):
-
while True:
-
# 从队列中获取下一个执行任务
-
task = self.task_queue.get()
-
# 执行
-
task.run()
-
# 执行完后放入队列的末尾
-
self.task_queue.put(task)
-
-
def foo(arg):
-
i = 0
-
while True:
-
time.sleep(1)
-
print(arg, i)
-
i += 1
-
yield #主动调度
-
-
def bar(arg):
-
i = 0
-
while True:
-
time.sleep(1)
-
print(arg, i)
-
i += 1
-
yield #主动调度
-
-
s = Scheduler()
-
s.create_task("foo", foo, "foo")
-
s.create_task("bar", bar, "bar")
-
s.loop()
运行
-
>>>
-
foo 0
-
bar 0
-
foo 1
-
bar 1
-
foo 2
-
bar 2
-
foo 3
-
bar 3
-
foo 4
-
bar 4
-
foo 5
-
bar 5
代码的说明已经在代码注释中,不再赘述。
这个简单的调度器程序基本实现了对协程的调度,但是实现的非常简陋,如任务不能退出,不能在任务中创建任务等,有兴趣的读者可以自行改善。
转载请注明出处:http://blog.chinaunix.net/uid-24716553-id-5727273.html
阅读(4490) | 评论(0) | 转发(0) |