协程——uthread学习
- uthread说明
- 细节
- uthread代码
ucontext-人人都可以实现的简单协程库
github地址
vscode c++调试环境搭建
程序员应如何理解协程
在此记录一下协程的基本概念,后续再考虑实现手写的协程。
uthread说明
一个简单的C++用户级线程(协程)库
- 一个调度器可以拥有多个协程
- 通过
uthread_create
创建一个协程 - 通过
uthread_resume
运行或者恢复运行一个协程 - 通过
uthread_yield
挂起一个协程,并切换到主进程中 - 通过
schedule_finished
判断调度器中的协程是否全部运行完毕 - 每个协程最多拥有128Kb的栈,增大栈空间需要修改源码的宏
DEFAULT_STACK_SIZE
,并重新编译
更详细的介绍,请查看我的中文博客 人既无名的专栏.
细节
-
ctx保存协程的上下文,stack为协程的栈,栈大小默认为DEFAULT_STACK_SZIE=128Kb.你可以根据自己的需求更改栈的大小。
-
func为协程执行的用户函数,arg为func的参数
-
state表示协程的运行状态,包括FREE,RUNNABLE,RUNING,SUSPEND,分别表示空闲,就绪,正在执行和挂起四种状态。
-
调度器包括主函数的上下文main,包含当前调度器拥有的所有协程的vector类型的threads,以及指向当前正在执行的协程的编号running_thread.如果当前没有正在执行的协程时,running_thread=-1.
下面给出一个简单例子,可以自己打断点调试一下,最重要的点:我感觉恢复上下文的时候,是恢复到下一行!我们只需要关注上下文之间的跳转即可!!!
下面也给出调试的配置launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) 启动",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/test1",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}
重点是这一行 "program": "${workspaceFolder}/test1"
uthread代码
总共就只有四个文件,这里都给出来,加了一点自己的注释理解
Makefile
all:
g++ uthread.cpp -g -c
g++ main.cpp -g -o main uthread.o
clean:
rm -f uthread.o main
uthread.h
/**
* @file thread.h
* @author chenxueyou
* @version 0.1
* @brief :A asymmetric coroutine library for C++
* History
* 1. Date: 2014-12-12
* Author: chenxueyou
* Modification: this file was created
*/
#ifndef MY_UTHREAD_H
#define MY_UTHREAD_H
#ifdef __APPLE__
#define _XOPEN_SOURCE
#endif
#include <ucontext.h>
#include <vector>
#define DEFAULT_STACK_SZIE (1024*128)
#define MAX_UTHREAD_SIZE 1024
enum ThreadState{FREE,RUNNABLE,RUNNING,SUSPEND};
struct schedule_t;
typedef void (*Fun)(void *arg);
typedef struct uthread_t
{
ucontext_t ctx;
Fun func;
void *arg;
enum ThreadState state;
char stack[DEFAULT_STACK_SZIE];
}uthread_t;
typedef struct schedule_t
{
ucontext_t main;
int running_thread;
uthread_t *threads;
int max_index; // 曾经使用到的最大的index + 1
schedule_t():running_thread(-1), max_index(0) {
threads = new uthread_t[MAX_UTHREAD_SIZE];
for (int i = 0; i < MAX_UTHREAD_SIZE; i++) {
threads[i].state = FREE;
}
}
~schedule_t() {
delete [] threads;
}
}schedule_t;
/*help the thread running in the schedule*/
static void uthread_body(schedule_t *ps);
/*Create a user's thread
* @param[in]:
* schedule_t &schedule
* Fun func: user's function
* void *arg: the arg of user's function
* @param[out]:
* @return:
* return the index of the created thread in schedule
*/
int uthread_create(schedule_t &schedule,Fun func,void *arg);
/* Hang the currently running thread, switch to main thread */
void uthread_yield(schedule_t &schedule);
/* resume the thread which index equal id*/
void uthread_resume(schedule_t &schedule,int id);
/*test whether all the threads in schedule run over
* @param[in]:
* const schedule_t & schedule
* @param[out]:
* @return:
* return 1 if all threads run over,otherwise return 0
*/
int schedule_finished(const schedule_t &schedule);
#endif
uthread.cpp
/**
* @file uthread.cpp
* @author chenxueyou
* @version 0.1
* @brief :A asymmetric coroutine library for C++
* History
* 1. Date: 2014-12-12
* Author: chenxueyou
* Modification: this file was created
*/
#ifndef MY_UTHREAD_CPP
#define MY_UTHREAD_CPP
#include "uthread.h"
//#include <stdio.h>
void uthread_resume(schedule_t &schedule , int id)
{
if(id < 0 || id >= schedule.max_index){
return;
}
uthread_t *t = &(schedule.threads[id]);
// 如果id对应的协程状态是挂起,就激活它的上下文,然后保存当前的上下文
if (t->state == SUSPEND) {
swapcontext(&(schedule.main),&(t->ctx));
}
}
void uthread_yield(schedule_t &schedule)
{
// 只有当前有协程在运行,才能yield
if(schedule.running_thread != -1 ){
uthread_t *t = &(schedule.threads[schedule.running_thread]);
t->state = SUSPEND;
schedule.running_thread = -1;
// 和resume正好相反
swapcontext(&(t->ctx),&(schedule.main));
}
}
// 用户函数
void uthread_body(schedule_t *ps)
{
int id = ps->running_thread;
// 说明有执行的协程,取出来,执行完了以后设置为空闲
if(id != -1){
uthread_t *t = &(ps->threads[id]);
// 这里是执行用户函数,比如main.cpp中的func2和func3,执行完了回到这里
t->func(t->arg);
t->state = FREE;
ps->running_thread = -1;
}
}
int uthread_create(schedule_t &schedule,Fun func,void *arg)
{
int id = 0;
// 如果有空闲的协程,就退出,记录当前这个空闲的id
for(id = 0; id < schedule.max_index; ++id ){
if(schedule.threads[id].state == FREE){
break;
}
}
// 如果是最大id,就要更新最大索引了,设置为runable就绪
if (id == schedule.max_index) {
schedule.max_index++;
}
uthread_t *t = &(schedule.threads[id]);
t->state = RUNNABLE;
t->func = func;
t->arg = arg;
getcontext(&(t->ctx));
// 设置协程上下文,下一个上下文为schedule里面保存的main上下文
t->ctx.uc_stack.ss_sp = t->stack;
t->ctx.uc_stack.ss_size = DEFAULT_STACK_SZIE;
t->ctx.uc_stack.ss_flags = 0;
t->ctx.uc_link = &(schedule.main);
schedule.running_thread = id;
// 将 uthread_body 函数的地址转换为一个无参数且无返回值的函数指针
// uthread_body 是一个用户定义的函数,将在新线程中执行。
makecontext(&(t->ctx),(void (*)(void))(uthread_body),1,&schedule);
swapcontext(&(schedule.main), &(t->ctx));
return id;
}
// 检查协程调度器中的所有协程是否都已完成执行
// 0代表还有协程没有执行完成,1代表都执行完成
int schedule_finished(const schedule_t &schedule)
{
if (schedule.running_thread != -1){
return 0;
}else{
for(int i = 0; i < schedule.max_index; ++i){
if(schedule.threads[i].state != FREE){
return 0;
}
}
}
return 1;
}
#endif
main.cpp
#include "uthread.h"
#include <stdio.h>
void func1(void * arg)
{
puts("1");
puts("11");
puts("111");
puts("1111");
}
void func2(void * arg)
{
puts("22");
puts("22");
uthread_yield(*(schedule_t *)arg);
puts("22");
puts("22");
}
void func3(void *arg)
{
puts("3333");
puts("3333");
uthread_yield(*(schedule_t *)arg);
puts("3333");
puts("3333");
}
void context_test()
{
char stack[1024*128];
ucontext_t uc1,ucmain;
getcontext(&uc1);
uc1.uc_stack.ss_sp = stack;
uc1.uc_stack.ss_size = 1024*128;
uc1.uc_stack.ss_flags = 0;
uc1.uc_link = &ucmain;
makecontext(&uc1,(void (*)(void))func1,0);
swapcontext(&ucmain,&uc1);
puts("main");
}
void schedule_test()
{
schedule_t s;
int id1 = uthread_create(s,func3,&s);
int id2 = uthread_create(s,func2,&s);
while(!schedule_finished(s)){
uthread_resume(s,id2);
uthread_resume(s,id1);
}
puts("main over");
}
int main()
{
context_test();
puts("----------------");
schedule_test();
return 0;
}
输出如下所示