libgo开源协程源码剖析

173 阅读2分钟

测试文件

#include "coroutine.h"
#include "win_exit.h"
#include <stdio.h>
#include <thread>

void foo()
{
    printf("function pointer\n");
}

struct A {
    void fA() { printf("std::bind\n"); }
    void fB() { printf("std::function\n"); }
};


int main()
{
    //----------------------------------
    // 使用关键字go创建协程, go后面可以使用:
    //     1.void(*)()函数指针, 比如:foo.
    //     2.也可以使用无参数的lambda, std::bind对象, function对象, 
    //     3.以及一切可以无参调用的仿函数对象
    //   注意不要忘记句尾的分号";".
    go foo;  
    
    go []{
        printf("lambda\n");
    };

    go std::bind(&A::fA, A());

    std::function<void()> fn(std::bind(&A::fB, A()));
    go fn;

    // 也可以使用go_stack创建指定栈大小的协程
    //   创建拥有10MB大栈的协程
    go co_stack(10 * 1024 * 1024) []{
        printf("large stack\n");
    };
    
    // 创建单独线程启动调度器
    std::thread t([]{ co_sched.Start(); });
    t.detach();
    while(true);
    
    return 0;
}

上面一共用到了三种方式创建协程:

  • 普通函数
  • lambda
  • 成员函数
  • 使用function

go关键字是什么东西

作者是把go这个关键字抽象成了什么,可以这样创建线程,指定协程的函数

image.png 其实就是作者创建了一个结构体__go,然后定义了减号运算符函数,参数接受的都是函数指针类型. 所以,go foo;这个代码就相当于:

__goo(文件名, 行号) - foo;

libgo协程的结构

协程调度器结构

image.png

  1. Scheduler负责管理DispatcherProcesser,默认会提供一个Scheduler名为co_sched. 协程只会在所属的调度器中被调度,创建额外的调度器可以实现业务间的隔离

  2. processer负责协程调度,每一个processer维护了4个队列

  • newQueue_: 队列存放新加入的协程,包括新创建的协程,唤醒挂起的协程,还有 steal(偷) 来的协程
  • runnableQueue_: 可运行协程队列
  • waitQueue_: 存放挂起的协程
  • gcQueue: 存放需要gc的协程
  1. Dispatcher负责不同processer上的协程的负载均衡,实现协程的跨线程迁移steal, 增加新的processer等. 只有在processer数量大于1的时候才会创建Dispatcher线程

未完待续...