1 什么是协程
定义
- 协程可以理解为用户态轻量级线程;
- 协程拥有自己的上下文和栈;
- 协程的切换和调度由用户定义,不用陷入内核;
- 如同一个进程拥有多个线程,一个线程可以拥有多个协程。
优点
- 极高的执行效率 因为协程切换不用陷入内核,是由用户程序定义切换逻辑,因此协程没有线程切换的开销。
- 以同步代码的方式写异步逻辑 可开发异步IO。
缺点
由于协程是在单个线程内切换的,无法利用多核资源。结合多进程/多线程可以解决这个问题。
2 协程的实现
协程在一些脚本语言如Python、Lua中都已经很好地支持了(C++20也支持协程),但为了更好地学习它,还是有必要去逐步封装一个协程。本文主要利用Linux的ucontext库去封装一个Coroutine类,再与boost的基于fcontext的Coroutine库做一个对比。
2.1 基于ucontext的实现
ucontext.h 简介
头文件<ucontext.h>
提供以下4个调用:
int getcontext(ucontext_t * ucp);
int setcontext(const ucontext_t *ucp);
void makecontext(ucontext_t *ucp, void(*func)(), int argc, ...);
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
以下逐个介绍它们:
int getcontext(ucontext_t * ucp);
- 获取当前上下文, 初始化ucp结构体, 将当前上下文保存到ucp中;
- 成功不返回,失败返回-1, 并设置errno。
void makecontext(ucontext_t *ucp, void(*func)(), int argc, ...);
- 创建一个目标上下文 ,用于初始化一个协程,并且上下文需要
ucp
来自getcontext
调用;- 指定分配给上下文的栈
uc_stack.ss_sp
; - 指定这块栈的大小
uc_stack.ss_size
,如32K, 64K, 128K; - 指定
uc_stack.ss_flags
,一般为0; - 指定后继上下文
uc_link
。
int setcontext(const ucontext_t *ucp);
- 设置当前的上下文为
ucp
; - 若
ucp
来自getcontext
, 那么上下文恢复至ucp
; ucp
来自makecontext
, 那么将会调用makecontext
函数的第二个参数指向的函数func
, 如果func
返回, 则恢复至ucp->uc_link
指定的后继上下文, 如果该ucp
中的uc_link
为NULL
, 那么线程退出;- 成功不返回, 失败返回-1, 设置errno。
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
- 切换上下文
- 保存当前上下文至
oucp
, 恢复ucp
上下文(先执行makecontext指定的ucp入口函数, 而后会执行ucp->uc_link指向的后继上下文); - 成功不返回, 失败返回-1, 设置errno。
Coroutine类的实现
主要需要实现yield语义和resume语义,即协程主动让出控制权和恢复某个特定的协程。另外,需要定义4种状态,即INIT(初始)、RUNNING(运行)、SUSPEND(挂起)、TERM(结束),便于切换时检查协程的状态。由于协程是在线程内切换的,类似进程拥有一个主线程,线程内也应拥有一个主协程,用线程局部变量(thread_local
)定义。
coroutine.h
#ifndef _COROUTINE_H_
#define _COROUTINE_H_
#include <memory>
#include <functional>
#include <ucontext.h>
class Coroutine : public std::enable_shared_from_this<Coroutine> {
public:
typedef std::shared_ptr<Coroutine> ptr;
typedef std::function<void()> Callback;
enum State {
INIT, // 初始状态
RUNNING, // 运行中状态
SUSPEND, // 挂起状态
TERM // 结束状态
};
Coroutine(Callback cb, size_t stacksize = 0);
~Coroutine();
uint64_t getId() {
return id_; }
State getState() {
return state_; }
void swapOut();
void swapIn();
public:
static void Resume(const Coroutine::ptr crt);
static void Yield();
static void SetThis(Coroutine* crt);
static Coroutine::ptr GetThis();
static void MainFunc();
private:
Coroutine();
uint64_t id_ = 0;
uint32_t stacksize_ = 0;
State state_ = INIT;
ucontext_t ctx_;
void* stack_ = nullptr; // 协程堆栈
Callback cb_;
struct Comparator {
bool operator()(const Coroutine::ptr& lhs, const Coroutine::ptr& rhs) const;
};
};
#endif
coroutine.cpp
#include "coroutine.h"
#include <assert.h>
#include