之前一直没想明白了一个问题, 就是关于协程如何进行上下文切换。众所周知, 协程是分为有栈协程和无栈协程俩种. 区别在于是否有自己的调用栈来进行函数调用等操作.
有栈协程
有栈协程这里的做法比较好理解, 一般来说有俩种做法:
static void context_swap(struct Context* prev_, struct Context* next_) { // store in .data, avoid copying stack frame to the new stack static uint64_t eip; static volatile struct Context* prev; static volatile struct Context* next; prev = prev_; next = next_; eip = next->regs.rip; // store current context in prev REG_STORE(rax, prev); REG_STORE(rbx, prev); REG_STORE(rcx, prev); REG_STORE(rdx, prev); REG_STORE(rsi, prev); REG_STORE(rdi, prev); REG_STORE(r8, prev); REG_STORE(r9, prev); REG_STORE(r10, prev); REG_STORE(r11, prev); REG_STORE(r12, prev); REG_STORE(r13, prev); REG_STORE(r14, prev); REG_STORE(r15, prev); REG_STORE(rbp, prev); REG_STORE(rsp, prev); // jump to $eip_to_store asm volatile ("movq $eip_to_store, %0" : "=m" (prev->regs.rip)); // resotre context from next REG_RESTORE(rax, next); REG_RESTORE(rbx, next); REG_RESTORE(rcx, next); REG_RESTORE(rdx, next); REG_RESTORE(rsi, next); REG_RESTORE(rdi, next); REG_RESTORE(r8, next); REG_RESTORE(r9, next); REG_RESTORE(r10, next); REG_RESTORE(r11, next); REG_RESTORE(r12, next); REG_RESTORE(r13, next); REG_RESTORE(r14, next); REG_RESTORE(r15, next); REG_RESTORE(rbp, next); REG_RESTORE(rsp, next); asm volatile ("jmpq *%0\n\t" : : "m" (eip)); // label: eip_to_store asm volatile("eip_to_store:\n"); }
这样每个协程切换的时候, 整个栈都会被切换, 看起来和线程没啥区别, 只是调度一个发生在用户态可以由用户控制, 一个发生在内核态由系统控制.
无栈协程
有栈协程的做法比较好理解, 那无栈线程是怎么做的呢?无栈协程是怎么利用vm自带的栈完成上下文切换? 他的状态报存在哪里呢?
首先, 先说结论, 无栈协程的实现, 要几个条件: 1. 栈帧内保存的不是状态而是指向状态的指针. 2. 所有帧的状态保存在堆上
为什么说第二点比较重要, 因为理解了第二点, 就发现, 其实根本不需要上下文切换, 因为全局的上下文就没变过, 改变他们的调用关系就行(栈)
例子:
我们有几行这个代码 假设每个函数都是10行字节码
```python def gen(): # code yield # 第3行字节码 #code return # 第10行字节码 def f1(): #code g1 = gen() next(g1) # 第3行字节码 #code next(g1) # 第8行字节码 #code return # 第10行字节码 f1() ```
我们把每个代码的执行的状态(帧) 叫PyFrame,
struct PyFrame { // 别的不重要 *state //状态 code // 当前执行到了第几行 }
我们栈的状态大致如下
注意第4步 g1的第二次入栈, 这时候, g1代表的PyFrame 的状态 在堆上, 所以压进来的时候 code就到了上次执行的第三行加一第四行了
区别
有栈协程可以随意的切换, 因为他所有状态都在他协程内部, 并且可以并行 , 存在中间状态比如寄存器的计算结果啥的, 切换要很小心, 但是粒度更细
无栈协程只能手动切换, 不过效率要高, 不用管复杂的寄存器状态, 切换的控制权也在用户手中