把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行。如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程

我们知道线程的调度(线程上下文切换)是由操作系统决定的,当一个线程启动后,什么时候占用CPU、什么时候让出CPU,程序员都无法干涉。假设现在启动4个线程,CPU线程时间片为 5 毫秒,也就是说,每个线程每隔5ms就让出CPU,让其他线程抢占CPU。可想而知,等4个线程运行结束,要进行多少次切换?

如果我们能够自行调度自己写的程序,让一些代码块遇到IO操作时,切换去执行另外一些需要CPU操作的代码块,是不是节约了很多无畏的上下文切换呢?是的,协程就是针对这一情况而生的。我们把写好的一个应用程序分为很多个代码块,如下图所示:

把应用程序的代码分为多个代码块,正常情况代码自上而下顺序执行。如果代码块A运行过程中,能够切换执行代码块B,又能够从代码块B再切换回去继续执行代码块A,这就实现了协程(通常是遇到IO操作时切换才有意义)。示意图如下:

所以,关于协程可以总结以下两点:

(1)线程的调度是由操作系统负责,协程调度是程序自行负责。

(2)与线程相比,协程减少了无畏的操作系统切换。

实际上当遇到IO操作时做切换才更有意义,(因为IO操作不用占用CPU),如果没遇到IO操作,按照时间片切换,无意义。

python中的yield 关键字用来实现生成器,但是生成器在一定的程度上与协程其实也是差不多。我们来看个例子:

def sayHello(n):
while n > 0:
print("hello~", n)
yield n
n -= 1
print('say hello') if __name__ == "__main__":
sayHello(5) # 测试1
# next(sayHello(5)) # 测试2 # 测试3
# for i in sayHello(5):
# pass

挨个测试,你会发现第一个测试是不能通过的,什么都不会输出,这就是我们的生成器特性了,一旦函数内部有yield关键字,此函数就是生成器,只有调用next 或是 for之类的能够迭代的才能够使得生成器执行。那么这与我们的协程有什么关系呢?请看代码:

from collections import deque

def sayHello(n):
while n > 0:
print("hello~", n)
yield n
n -= 1
print('say hello') def sayHi(n):
x = 0
while x < n:
print('hi~', x)
yield
x += 1
print("say hi") # 使用yield语句,实现简单任务调度器
class TaskScheduler(object):
def __init__(self):
self._task_queue = deque() def new_task(self, task):
'''
向调度队列添加新的任务
'''
self._task_queue.append(task) def run(self):
'''
不断运行,直到队列中没有任务
'''
while self._task_queue:
task = self._task_queue.popleft()
try:
next(task)
self._task_queue.append(task)
except StopIteration:
# 生成器结束
pass if __name__ == "__main__":
sched = TaskScheduler()
sched.new_task(sayHello(10))
sched.new_task(sayHi(15))
sched.run()

代码运行下,你就发现了,这就是我们对协程的定义了。接下来我们说下actor模型。actor模式是一种最古老的也是最简单的并行和分布式计算解决方案。下面我们通过yield来实现:

from collections import deque

class ActorScheduler:
def __init__(self):
self._actors = {}
self._msg_queue = deque() def new_actor(self, name, actor):
self._msg_queue.append((actor, None))
self._actors[name] = actor def send(self, name, msg):
actor = self._actors.get(name)
if actor:
self._msg_queue.append((actor, msg)) def run(self):
while self._msg_queue:
# print("队列:", self._msg_queue)
actor, msg = self._msg_queue.popleft()
# print("actor", actor)
# print("msg", msg)
try:
actor.send(msg)
except StopIteration:
pass if __name__ == '__main__':
def say_hello():
while True:
msg = yield
print("say hello", msg) def say_hi():
while True:
msg = yield
print("say hi", msg) def counter(sched):
while True:
n = yield
print("counter:", n)
if n == 0:
break
sched.send('say_hello', n)
sched.send('say_hi', n)
sched.send('counter', n-1) sched = ActorScheduler()
# 创建初始化 actors
sched.new_actor('say_hello', say_hello())
sched.new_actor('say_hi', say_hi())
sched.new_actor('counter', counter(sched)) sched.send('counter', 10)
sched.run()

(1) ActorScheduler 负责事件循环
(2) counter() 负责控制终止
(3) say_hello() / say_hi() 相当于切换的协程,当程序运行到这些函数内部的yield处,就开始切换。

所以,当执行时,我们能够看到say_hello() / say_hi()不断交替切换执行,直到counter满足终止条件之后,协程终止。看懂上例可能需要花费一些时间。实际上我们已经实现了一个“操作系统”的最小核心部分。 生成器函数(含有yield的函数)就是认为,而yield语句是任务挂起的信号。 调度器循环检查任务列表直到没有任务要执行为止。

而这就是廖雪峰的python官网教程里面的协程代码的最好解释,这也是之前一直在思考的问题,请看代码:

def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK' def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close() c = consumer()
produce(c)

我之前一直纳闷send()函数是如何激活生成器的,原来是实现了actor模型的协程!

相关链接:再议Python协程——从yield到asyncio

终结python协程----从yield到actor模型的实现的更多相关文章

  1. 再议Python协程——从yield到asyncio

    协程,英文名Coroutine.前面介绍Python的多线程,以及用多线程实现并发(参见这篇文章[浅析Python多线程]),今天介绍的协程也是常用的并发手段.本篇主要内容包含:协程的基本概念.协程库 ...

  2. 理解Python协程:从yield/send到yield from再到async/await

    Python中的协程大概经历了如下三个阶段:1. 最初的生成器变形yield/send2. 引入@asyncio.coroutine和yield from3. 在最近的Python3.5版本中引入as ...

  3. Python协程笔记 - yield

    生成器(yield)作为协程 yield实际上是生成器,在python 2.5中,为生成器增加了.send(value)方法.这样调用者可以使用send方法对生成器发送数据,发送的数据在生成器中会赋值 ...

  4. 从yield 到yield from再到python协程

    yield 关键字 def fib(): a, b = 0, 1 while 1: yield b a, b = b, a+b yield 是在:PEP 255 -- Simple Generator ...

  5. 用yield实现python协程

    刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...

  6. python协程--yield和yield from

    字典为动词“to yield”给出了两个释义:产出和让步.对于 Python 生成器中的 yield 来说,这两个含义都成立.yield item 这行代码会产出一个值,提供给 next(...) 的 ...

  7. 00.用 yield 实现 Python 协程

    来源:Python与数据分析 链接: https://mp.weixin.qq.com/s/GrU6C-x4K0WBNPYNJBCrMw 什么是协程 引用官方的说法: 协程是一种用户态的轻量级线程,协 ...

  8. Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)

    Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...

  9. python协程(yield、asyncio标准库、gevent第三方)、异步的实现

    引言 同步:不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,称这些程序单元是同步执行的. 例如购物系统中更新商品库存,需要用"行锁"作为通信信号,让不同的更新 ...

随机推荐

  1. webStorm破解

    B4A73YYJ-eyJsaWNlbnNlSWQiOiI0M0I0QTczWVlKIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiYXNzaWduZWVOYW1lIjoiIiw ...

  2. leetcode 37. Sudoku Solver 36. Valid Sudoku 数独问题

    三星机试也考了类似的题目,只不过是要针对给出的数独修改其中三个错误数字,总过10个测试用例只过了3个与世界500强无缘了 36. Valid Sudoku Determine if a Sudoku ...

  3. Linux2.6--Linus电梯

          内核为了处理来自IO层的请求,需要进行相应的优化,因为当请求很多时,且请求的块又都几种在一块,那么如果按照顺序处理这些请求无疑是很大的时间开销,所以,我们需要寻求方法来处理这种情况(当然, ...

  4. 保证service存活

    Android开发的过程中,每次调用startService(Intent)的时候,都会调用该Service对象的onStartCommand(Intent,int,int)方法,然后在onStart ...

  5. Oracle使用游标查询指定数据表的所有字段名称组合而成的字符串

    应用场合:参考网上查询数据表的所有字段名代码,使用游标生成指定单个表的所有字段名跟逗号组成的用于select  逗号隔开的字段名列表 from字符串等场合. 查询结果输出如下: 当前数据表TB_UD_ ...

  6. SpringMVC系列之(一) 入门实例

    Spring MVC是非常优秀的MVC框架,由其是在3.0版本发布后,现在有越来越多的团队选择了Spring3 MVC了.Spring MVC结构简单,应了那句话简单就是美,而且他强大不失灵活,性能也 ...

  7. javascript之event对象

    注意:以下给出的是在IE下的event事件说明,如果应用在非IE下可能会出现兼容性问题,需要结合具体的应用环境,使用兼容性的函数来处理 1.altKey 描述: 检查alt键的状态. 语法: even ...

  8. ROS_Kinetic_20 ROS基础补充

    ROS_Kinetic_20 ROS基础补充 1 手动创建ROS功能包 参考官网:http://wiki.ros.org/cn/ROS/Tutorials/Creating%20a%20Package ...

  9. IMX51---GPIO

    GPIO(General Purpose Input/Output)指通用输入/输出,IMX51的GPIO模块提供32位双向的.通用输入和输出的信号,下图是GPIO的框图: 图1 1.      GP ...

  10. Cocos2D:塔防游戏制作之旅(十四)

    塔之战:炮塔的攻击 炮塔就位了?检查.敌人前进中?再次检查 - 它们看起来就是如此!看起来到了击溃这些家伙的时候了!这里我们将智能置入炮塔的代码中去. 每一个炮塔检查是否有敌人在其攻击范围.(炮塔一次 ...