Chinaunix首页 | 论坛 | 博客
  • 博客访问: 190747
  • 博文数量: 21
  • 博客积分: 250
  • 博客等级: 二等列兵
  • 技术积分: 470
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-06 23:10
个人简介

程序猿

文章分类

全部博文(21)

文章存档

2016年(17)

2014年(3)

2013年(1)

分类: Python/Ruby

2016-05-15 17:24:06

协程是一种比线程更轻量级的机制。
它可以使开发者暂停一个正在运行的函数,等到合适的时机再接着运行。
协程相对于线程的优势:
1. 调度时不需要上下文切换
2. 不需要线程锁,因为多个协程是可以运行在一个线程中的

为了理解Python协程,让我们先来了解Python中的迭代器和生成器。
在python中,可以用for ... in ... 格式访问的对象称为迭代器(iterables),比如list,string都是迭代器。
我们可以方便地一个一个读取迭代器中的数据:
  1. data = [x*x for x in range(5)]
  2. for i in data:
  3.     print(i)

但是,上面的做法会在执行时把迭代器中所有的数据读入内存。有时,当数据量很大时,我们可能并不想这么做。
这就引入了生成器(generators)。生成器是一种特殊的迭代器,它执行时不会把所有的迭代数据放入内存,而是保存获取数据的算法,和当前执行到的状态。
将上面例子的list改成生成器只需要把[]改成():
  1. data = (x*x for x in range(5))
  2. for i in data:
  3.     print(i)
data变成生成器后的一个“后果”是只能迭代一次,下一次迭代会取不到数据:
  1. >>> data = (x*x for x in range(3))
  2. >>> for i in data:
  3.     print(i)
  4. 0
  5. 1
  6. 4
  7. >>> for i in data:
  8.     print(i)

注意(x*x for x in range(5))类型是generator而不是tuple,可以用type()看到
  1. >>> type((x*x for x in range(5)))
  2. <class 'generator'>
如果要生成tuple类型的话,需要转换一下:
  1. >>> type(tuple(x*x for x in range(5)))
  2. <class 'tuple'>

我们也可以使用yield把普通函数变成生成器。
  1. >>> def fun():
  2.     for i in [x*x for x in range(5)]:
  3.         yield i
  4. >>> type(fun())
  5. <class 'generator'>

我们拿到生成器对象后,可以通过__next__()方法不断获取下一个值,当不再有新的值时,会抛出StopIteration异常。
  1. >>> f = fun()
  2. >>> f.__next__()
  3. 0
  4. >>> f.__next__()
  5. 1
  6. >>> f.__next__()
  7. 4
  8. >>> f.__next__()
  9. 9
  10. >>> f.__next__()
  11. 16
  12. >>> f.__next__()
  13. Traceback (most recent call last):
  14.   File "", line 1, in <module>
  15.     f.__next__()
  16. StopIteration

另外也能使用生成器的send()方法的参数作为生成器内部yield的返回值,具体可参考python文档


协程举例

协程一种常见的使用场景是管道(pipeline)。即B要用到A的结果,C又要用到B的结果,其中A为IO操作。最简单的做法就是顺序执行,A拿到所有的数据后传给B,B全部处理完再传给C。但是,当数据量很大时这种方法显然是不明智的。
我们会想到创建3个线程A,B,C,A拿到一定数据后发送给B,B和C在没有数据时阻塞等待,接收到数据就执行。这种方法能很好地实现我们的目标,但在python中,有更好的方法。
来看代码:
  1. import time

  2. def A():
  3.     for i in range(5):
  4.         time.sleep(1)
  5.         print('[A] producing %d' % i)
  6.         yield i


  7. def B(data):
  8.     for i in data:
  9.         print('[B] producing %d' % (i * i))
  10.         yield i * i

  11. def C(data):
  12.     for i in data:
  13.         if i % 2:
  14.             print('[C] producing %d' % i)

  15.     
  16. if __name__=='__main__':
  17.     a = A()
  18.     b = B(a)
  19.     C(b)
上面例子中的函数A()模拟费时的IO操作,B()将A生成的数据取平方,C()中判断值是不是奇数,是的话打印出来。
执行结果:
  1. >>>
  2. [A] producing 0
  3. [B] producing 0
  4. [A] producing 1
  5. [B] producing 1
  6. [C] producing 1
  7. [A] producing 2
  8. [B] producing 4
  9. [A] producing 3
  10. [B] producing 9
  11. [C] producing 9
  12. [A] producing 4
  13. [B] producing 16
要注意的是,当我们运行生成器函数的时候,其实函数并没有执行,而是返回一个生成器对象。对象被迭代时才会真正执行。

下面我们再实现一个简单的协程调度器。
  1. import time
  2. import queue

  3. class Task(object):
  4.     taskid = 0
  5.     def __init__(self, fun, args):
  6.         self.taskid = Task.taskid
  7.         Task.taskid += 1
  8.         # 获取task的生成器
  9.         self.instance = fun(args)

  10.     def run(self):
  11.         # 执行下一个迭代
  12.         self.instance.__next__()
  13.         
  14. class Scheduler(object):
  15.     def __init__(self):
  16.         self.tasks = {}
  17.         # 任务队列
  18.         self.task_queue = queue.Queue()

  19.     def create_task(self, name, fun, args):
  20.         task = Task(fun, args)
  21.         self.tasks[name] = task
  22.         self.task_queue.put(task)
  23.         
  24.     def loop(self):
  25.         while True:
  26.             # 从队列中获取下一个执行任务
  27.             task = self.task_queue.get()
  28.             # 执行
  29.             task.run()
  30.             # 执行完后放入队列的末尾
  31.             self.task_queue.put(task)

  32. def foo(arg):
  33.     i = 0
  34.     while True:
  35.         time.sleep(1)
  36.         print(arg, i)
  37.         i += 1
  38.         yield #主动调度

  39. def bar(arg):
  40.     i = 0
  41.     while True:
  42.         time.sleep(1)
  43.         print(arg, i)
  44.         i += 1
  45.         yield #主动调度

  46. s = Scheduler()
  47. s.create_task("foo", foo, "foo")
  48. s.create_task("bar", bar, "bar")
  49. s.loop()

运行
  1. >>>
  2. foo 0
  3. bar 0
  4. foo 1
  5. bar 1
  6. foo 2
  7. bar 2
  8. foo 3
  9. bar 3
  10. foo 4
  11. bar 4
  12. foo 5
  13. bar 5
代码的说明已经在代码注释中,不再赘述。
这个简单的调度器程序基本实现了对协程的调度,但是实现的非常简陋,如任务不能退出,不能在任务中创建任务等,有兴趣的读者可以自行改善。


转载请注明出处:http://blog.chinaunix.net/uid-24716553-id-5727273.html



阅读(2299) | 评论(0) | 转发(0) |
0

上一篇:golang文档解析

下一篇:golang 测试

给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册