Tornado协程原理

协程的定义

  • 函数
    入口:有且只有一个入口
    出口:有且只有一个出口

  • 协程
    入口:多个入口
    出口:多个出口
    特点:暂定,保留执行状态,恢复执行

协程是函数基础上,一种更加宽泛定义的计算机程序模块,它可以有多个入口点,允许从一个入口点到下个入口点之前暂停保留执行状态,等待合适的时机恢复执行状态,从下一个入口点重新开始性质

  • 协程代码块
    从一个入口点到下一个入口点的代码

  • 协程模块
    有n个入口代码,和n个协程代码块组成。其组织形式为:函数入口-》协程代码块-》入口点-》协程代码块...

可迭代对象,迭代器,生成器

可迭代对象(Iterable)

python中可迭代类型如字符串,列表,元组,字典等:

str = 'abc'
list = ['a', 'b', 'c']
set = ('a', 'b', 'c')
dict = {'a':1, 'b':2}

他们的特点是dir(object)实现了__iter__()函数

迭代器(Iterator)

迭代器的要求是dir(object)实现了__iter__()__next__()两个方法

生成器(generator)

生成器是特殊的迭代器,在迭代器的基础上包含yield关键字

总结

  • 迭代器必须实现迭代器协议__iter____next__
  • __iter__返回的对象是可迭代对象
  • 迭代器一定是可迭代对象,但可迭代对象不一定是迭代器,有可能迭代细节交给另一个类,这个类才是迭代器
  • 生成器一定是一个迭代器,同时也是迭代对象
  • 生成器是一种特殊的迭代器,包含关键字yield来实现懒惰计算,并是的外部影响生成器的执行成为可能

生成器和yield语义

生成器的定义:提供一种函数,能够返回中间结果给调用者,然后维护函数的局部状态,以便当函数离开后,也能恢复执行。
生成器是一个包含关键字yield表达式的函数。一个生成器是异步的,即生成器模块中含有阻塞代码,生成器的类型是types.GeneratorType

#生成器
def sum(total):
    total = 0
    while True:
        a = yield total
        total += a

if __name__ == '__main__':
    s = sum(10)      //获得生成器,但不执行生成器的代码
    print next(s)               //运行生成器的代码,执行yield total处,输出0
    print s.send(10)         //运行生成器的代码,第二次执行到yield total处,输出10
    print s.send(2)           //运行生成器的代码,第三次执行到yield total处,输出12

对上诉代码进行说明:

  • sum是一个生成器,包含关键字yield
  • sum生成器 a=yield从send(value)获取外部参数
  • sum生成器 yield total将total值传出
  • next(s)初始化生成器,并block在第一个yield中
  • s.send(v)唤醒生成器,并将值传入

Generator已经具备协程的能力,如能够暂停,保存状态,传出值;恢复执行,接受参数,异步执行。
但此时Generator还不是一个协程。一个真正的协程能够控制代码什么时候继续执行。而此时的Generator执行遇到一个yield还是把执行控制权转移给调用者

Future

Future顾名思义未来,将在未来被执行的代码。Future的类包含result,done, callback属性,以及_set_result, _set_done方法,通过这些属性和方法来实现当future被赋值后,能够执行对应的callback函数,具体代码如下:

class Future(object):
    def done(self):
        return self._done

    def result(self, timeout=None):
        self._clear_tb_log()
        if self._result is not None:
            return self._result
        if self._exc_info is not None:
            raise_exc_info(self._exc_info)
        self._check_done()
        return self._result

    def add_done_callback(self, fn):
        if self._done:
            fn(self)
        else:
            self._callbacks.append(fn)

    def set_result(self, result):
        self._result = result
        self._set_done()

    def _set_done(self):
        self._done = True
        for cb in self._callbacks:
            try:
                cb(self)
            except Exception:
                app_log.exception('exception calling callback %r for %r',
                                  cb, self)
        self._callbacks = None

Future说明
1.Future对象通过add_done_callback将回调函数与future对象进行绑定,目的是future被赋值后调用callback中的回调函数,从而实现激活生成器(回调函数调用gen.send(value)
2.def set_result(self, result):对Future对象赋值,将异常处理结果赋值给Future对象的result属性,同时执行_set_done遍历callback的函数
3.def _set_done(self):遍历callback的函数列表,并执行

总结来说,future对象绑定一个回调函数(回到函数会激活生成器),当future对象被赋值,会回调回调函数,进而回调函数会激活生成器(gen.send())

IOLoop类

IOLoop在协程运行环境中担任着协程调度的角色,消息循环本质上是一种事件循环,等待事件,然后运行对应事件的处理器。IOLoop主要调度处理的是IO事件(读,写,错误)以及callback和timeout。
IOLoop中注册future:add_future

def add_future(self, future, callback):
    assert is_future(future)
    callback = stack_context.wrap(callback)
    future.add_done_callback(lambda future: self.add_callback(callback, future))

IOLoop将callback和future作为参数传入IOLoop的add_callback,封装成匿名函数,并存储到future的callback属性中,当future被设置后匿名函数则会在下一个IOLoop中运行

  • 1.lambda future: self.add_callback(callback, future),将callback,future作为参数传入IOLoop的add_callback方法,实则利用偏函数将callback和future进行封包装,包装好的偏函数加入到IOLoop的回调函数列表中。当IOLoop下一次迭代运行,遍历回调函数并执行
    1. 匿名函数加入到future的callback属性中,当future对象被设置后,会调用回调函数

Coroutine函数装饰器

函数装饰器本质是一个函数,它将被调用的函数func作为参数,返回一个新的函数make_coroutine_wrapper。

def coroutine(func, replace_callback=True):
      return make_coroutine_wrapper(func, replace_callback=True)
image.png

如上图:

    1. result = func(*arg, **kwargs)获取函数对象,
    1. if isinstance(result, types.GeneratorType)判断result是否是生成器
    1. yielded = next(result) 因为result是生成器,当调用next会执行到生成器第一个yield代码,并返回一个future对象(异步操作)赋值给yield
    1. Runner(result, future, yielded),将生成器对象, yielded(future对象),future(监控future)传入函数Runner
class Runner(object):
    def __init__(self, gen, result_future, first_yielded):
        self.gen = gen
        self.result_future = result_future
        self.future = _null_future
        self.yield_point = None
        self.pending_callbacks = None
        self.results = None
        self.running = False
        self.finished = False
        self.had_exception = False
        self.io_loop = IOLoop.current()
        self.stack_context_deactivate = None
        if self.handle_yield(first_yielded):
            self.run()

    def run(self):
        if self.running or self.finished:
            return
        try:
            self.running = True
            while True:
                future = self.future
                if not future.done():
                    return
                self.future = None
                try:
                    try:
                        value = future.result()
                    except Exception:
                        self.had_exception = True
                        yielded = self.gen.throw(*sys.exc_info())
                    else:
                        yielded = self.gen.send(value)
                except (StopIteration, Return) as e:
                    self.finished = True
                    self.future = _null_future
                    self.result_future.set_result(getattr(e, 'value', None))
                    self.result_future = None
                    return
                except Exception:
                    self.finished = True
                    self.future = _null_future
                    self.result_future.set_exc_info(sys.exc_info())
                    self.result_future = None
                    return
                if not self.handle_yield(yielded):
                    return
        finally:
            self.running = False

    def handle_yield(self, yielded):

        try:
            self.future = convert_yielded(yielded)
        except BadYieldError:
            self.future = TracebackFuture()
            self.future.set_exc_info(sys.exc_info())

        if not self.future.done() or self.future is moment:
            self.io_loop.add_future(
                self.future, lambda f: self.run())
            return False
        return True

Runner函数内部处理就是将给yeilded(future对象)绑定一个回调函数,当future对象被set_result后,调用run;而run函数内部会调用生成器的send方法,并将vaule传入到生成器。
def handle_yield(self, yielded):

  • 1.将yielded转化为future对象
  • 2.调用IOLoop的add_future函数,将run函数添加到future的callback属性中
  • 3.当future对象在某处代码中被set_result,IOLoop下一个循环中便执行run函数
  • 4.run函数会取出future的result,并调用gen.send(value)启动生成器,并将生成器的输出在赋值给yielded
  • 5.重新赋值的yielded在作为参数传入handle_yield(self, yielded),循环步骤1,指导生成器结束

参考:
https://juejin.im/post/5ccafbf5e51d453a3a0acb42
https://blog.csdn.net/wyx819/article/details/45420017

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容