程序为了实现多任务,可以用多进程,多线程。但这两种,资源耗费较高。比如多线程,每一次切换,都会有成本。从一个线程a切到另一线程b,再切回线程a,操作系统是如何记住程序a执行位置的。其实在cpu中,有专门的寄存器存放程序执行位置。等切到线程b,系统会把线程a的执行上下文信息存在内存中。这样线程间切换就会有成本。为了解决这个问题。协程被提出来。
/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
?
第18-20行:18行调用next函数,进入栈帧对象。在栈帧对象中,执行栈帧对象的代码对象。当运行到YIELD_VALUE,会把相应值返回,然后退出当前栈帧对象,回到模块栈帧。这里控制权回到模块。现在执行19行,再次把相应栈帧压入虚拟机。然后执行函数代码对象,当执行到YIELD_VALUE,同样返回值 ,把控制权回到虚拟机模块级别。所以执行就在各自的生成器对象和模块间切换。实现了多任务的功能。
其实很好实现,每个生成器对象都有一个栈帧对象,而栈帧对象维护着一个变量记录当前执行代码的位置。所以当代码重新回到18行时,再次把之前生成器栈帧压入当前栈帧,读取代码执行位置,就能接着第5行代码执行。