现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

理解有栈无栈协程

2017-05-10 07:26 工业·编程 ⁄ 共 1957字 ⁄ 字号 暂无评论

之前一直没想明白了一个问题, 就是关于协程如何进行上下文切换。众所周知, 协程是分为有栈协程和无栈协程俩种. 区别在于是否有自己的调用栈来进行函数调用等操作.

有栈协程

有栈协程这里的做法比较好理解, 一般来说有俩种做法:

  • 采用操作系统提供的api 类似 ucontext 或者 setjump longjump
  • 用汇编操控寄存器保存状态 典型的例子
  • 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就到了上次执行的第三行加一第四行了

    区别

    有栈协程可以随意的切换, 因为他所有状态都在他协程内部, 并且可以并行 , 存在中间状态比如寄存器的计算结果啥的, 切换要很小心, 但是粒度更细

    无栈协程只能手动切换, 不过效率要高, 不用管复杂的寄存器状态, 切换的控制权也在用户手中

    给我留言

    留言无头像?