研究python解释器(2)-生成器,协程背后的运行机制

程序为了实现多任务,可以用多进程,多线程。但这两种,资源耗费较高。比如多线程,每一次切换,都会有成本。从一个线程a切到另一线程b,再切回线程a,操作系统是如何记住程序a执行位置的。其实在cpu中,有专门的寄存器存放程序执行位置。等切到线程b,系统会把线程a的执行上下文信息存在内存中。这样线程间切换就会有成本。为了解决这个问题。协程被提出来。

这里提出个问题:协程在虚拟机层面的工作原理是什么?

协程的工作原理:

我们看下面代码:

def co_process(arg):
    print('task with argument {} started'.format(arg))

    data = yield 1
    print('step one finished, got {} from caller'.format(data))

    data = yield 2
    print('step two finished, got {} from caller'.format(data))

    data = yield 3
    print('step three finished, got {} from caller'.format(data))

genco1 = co_process('1')
genco2 = co_process('2')
genco3 = co_process('3')

while True:
    next(genco1)
    next(genco2)
    next(genco3)

再看它们背后的字节码:

/usr/local/bin/python3.9 /Users/tanliang/PycharmProjects/python源码剖析/compile.py 
  1           0 LOAD_CONST               0 (<code object co_process at 0x10acdbdf0, file "circle.py", line 1>)
              2 LOAD_CONST               1 ('co_process')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (co_process)

 13           8 LOAD_NAME                0 (co_process)
             10 LOAD_CONST               2 ('1')
             12 CALL_FUNCTION            1
             14 STORE_NAME               1 (genco1)

 14          16 LOAD_NAME                0 (co_process)
             18 LOAD_CONST               3 ('2')
             20 CALL_FUNCTION            1
             22 STORE_NAME               2 (genco2)

 15          24 LOAD_NAME                0 (co_process)
             26 LOAD_CONST               4 ('3')
             28 CALL_FUNCTION            1
             30 STORE_NAME               3 (genco3)

 18     >>   32 LOAD_NAME                4 (next)
             34 LOAD_NAME                1 (genco1)
             36 CALL_FUNCTION            1
             38 POP_TOP

 19          40 LOAD_NAME                4 (next)
             42 LOAD_NAME                2 (genco2)
             44 CALL_FUNCTION            1
             46 POP_TOP

 20          48 LOAD_NAME                4 (next)
             50 LOAD_NAME                3 (genco3)
             52 CALL_FUNCTION            1
             54 POP_TOP
             56 JUMP_ABSOLUTE           32
             58 LOAD_CONST               5 (None)
             60 RETURN_VALUE

Disassembly of <code object co_process at 0x10acdbdf0, file "circle.py", line 1>:
  2           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('task with argument {} started')
              4 LOAD_METHOD              1 (format)
              6 LOAD_FAST                0 (arg)
              8 CALL_METHOD              1
             10 CALL_FUNCTION            1
             12 POP_TOP

  4          14 LOAD_CONST               2 (1)
             16 YIELD_VALUE
             18 STORE_FAST               1 (data)

  5          20 LOAD_GLOBAL              0 (print)
             22 LOAD_CONST               3 ('step one finished, got {} from caller')
             24 LOAD_METHOD              1 (format)
             26 LOAD_FAST                1 (data)
             28 CALL_METHOD              1
             30 CALL_FUNCTION            1
             32 POP_TOP

  7          34 LOAD_CONST               4 (2)
             36 YIELD_VALUE
             38 STORE_FAST               1 (data)

  8          40 LOAD_GLOBAL              0 (print)
             42 LOAD_CONST               5 ('step two finished, got {} from caller')
             44 LOAD_METHOD              1 (format)
             46 LOAD_FAST                1 (data)
             48 CALL_METHOD              1
             50 CALL_FUNCTION            1
             52 POP_TOP

 10          54 LOAD_CONST               6 (3)
             56 YIELD_VALUE
             58 STORE_FAST               1 (data)

 11          60 LOAD_GLOBAL              0 (print)
             62 LOAD_CONST               7 ('step three finished, got {} from caller')
             64 LOAD_METHOD              1 (format)
             66 LOAD_FAST                1 (data)
             68 CALL_METHOD              1
             70 CALL_FUNCTION            1
             72 POP_TOP
             74 LOAD_CONST               0 (None)
             76 RETURN_VALUE
None

Process finished with exit code 0
 

分析字节码:

第1行:定义一个函数对象:co_process。

第13-15行:调用函数对象,生成generator对象。

第18-20行:18行调用next函数,进入栈帧对象。在栈帧对象中,执行栈帧对象的代码对象。当运行到YIELD_VALUE,会把相应值返回,然后退出当前栈帧对象,回到模块栈帧。这里控制权回到模块。现在执行19行,再次把相应栈帧压入虚拟机。然后执行函数代码对象,当执行到YIELD_VALUE,同样返回值 ,把控制权回到虚拟机模块级别。所以执行就在各自的生成器对象和模块间切换。实现了多任务的功能。

虚拟机通过一个while循环,重新回到18行执行时,为什么能接着第5行代码执行,好像有记忆功能似的?

其实很好实现,每个生成器对象都有一个栈帧对象,而栈帧对象维护着一个变量记录当前执行代码的位置。所以当代码重新回到18行时,再次把之前生成器栈帧压入当前栈帧,读取代码执行位置,就能接着第5行代码执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值