- 参考
ucontext.h上下文切换 - 上下文结构体定义
-
mcontext_t类型与机器相关,并且不透明.ucontext_t结构体则至少拥有以下几个域:typedef struct ucontext {
struct ucontext *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
...
} ucontext_t; - 当当前上下文(如使用makecontext创建的上下文)运行终止时系统会恢复
uc_link指向的上下文;uc_sigmask为该上下文中的阻塞信号集合;uc_stack为该上下文中使用的栈;uc_mcontext保存的上下文的特定机器表示,包括调用线程的特定寄存器等四个操作函数
int getcontext(ucontext_t *ucp);- 初始化ucp结构体,将当前的上下文保存到ucp中
int setcontext(const ucontext_t *ucp);- 设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得,如果调用成功则不返回。
- 如果上下文是通过调用getcontext()取得,程序会继续执行这个调用。如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,如果func函数返回,则恢复makecontext第一个参数指向的上下文第一个参数指向的上下文context_t中指向的uc_link
- 如果uc_link为NULL,则线程退出。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);-
makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link. - 当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出
-
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);- 保存当前上下文到oucp结构体中,然后激活upc上下文。
- 如果执行成功,
getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对于的errno.
| 函数名 | 作用 |
|---|---|
| getcontext | 获取上下文 |
| setcontext | 设置上下文 |
| swapcontext | 保存当前上下文,切换上下文 |
| makecontext | 创建新的上下文 |
- 例子
int main(int argc, const char *argv[]){
ucontext_t context;
getcontext(&context);
puts("Hello world");
sleep(1);
setcontext(&context);
return 0;
} - 上述函数会不断重复输出
Hello World,因为getcontext将上下文设置在了输出之前,setcontext每次都把整个执行流返回到输出的位置
更换为执行时间最短先服务逻辑的调度器版本
- 参考的原版
- 我改进代码的github仓库
- 使用了优先级队列重写携程选择逻辑,切换的时候会选择当前已经运行时间最短的携程上处理机执行
- 运行结果
- 修改为了多线程版本,分别调度,分别设置优先级
注意事项
- C/C++文件编译的时候每个源文件都是独立编译的,这样会导致即使头文件使用了
ifndef之类的保护,仍然可能在整个项目中被不同的文件引用多次- 因此如果头文件中出现了函数或者是变量的定义的话,这个变量在整个项目中会被定义多次
- 因此头文件中只能声明,变量使用
extern关键字声明 - 必须定义的函数用
inline
- 注意类的静态
static成员变量必须在某个源文件中定义,才能在其他源文件中使用类名和作用域运算符::访问 - C++可以使用宏
__FILE__判断自己所处的文件名称 makecontext的第二个参数无论传入的是什么函数,都要将其转换为void(*)(void)类型的函数指针,然后再给出参数- 执行流路径
-----------调度器或main中-----------------
创建协程(create)-->
获取互斥锁
使用makecontext生成开始执行协程函数的上下文
令目标函数的上下文的后继位置时刻指向调度器schedule的上下文main
释放互斥锁
切换到协程(resume)-->
将当前的上下文保存在调度器schedule.main中,切换到目标函数的上下文
-----------协程函数中----------------
切换回main(yield)-->
获取锁
将协程当前的上下文保存在ctx中
释放锁
切换回之前指向的main上下文
-----------------main中-----------------
... - 因为添加协程
create函数与协程调度器不在一个线程中,可能会有竞争关系,因此添加了互斥锁防止冲突
<setjmp.h>实现切换
setjmp 函数:
setjmp用于保存当前程序的执行状态,并返回一个整数值。当首次调用
setjmp时,它返回0,表示保存了当前执行状态。当从
longjmp调用返回时,setjmp返回一个非零值,通常用于区分正常返回和通过longjmp返回。longjmp函数longjmp用于恢复之前由setjmp保存的执行状态。它接受两个参数:保存的执行状态(由
setjmp返回的值)和一个非零的返回值。调用
longjmp会导致程序跳转到相应setjmp处,并且setjmp返回的值为longjmp的返回值。一个例子
-
jmp_buf env;
int my_func(int a, int b) {
if (b == 0) {
printf("do not allow division by 0\n");
longjmp(env, 1);
}
return a / b;
}
int main(int argc, char const *argv[]) {
int res = setjmp(env);
if (res == 0) {
printf("return from setjmp\n");
my_func(10, 0);
} else {
printf("return from longjmp: %d\n", res);
}
return 0;
}C++语言的锁机制
unique_lock #include <mutex>和#include <thread>std::unique_lock<std::mutex> lock(myMutex); // 构造 unique_lock,并锁定互斥量
构造就是加锁
析构就是解锁
lock.lock(); // 手动锁定互斥量
// 锁定期间的其他操作...
lock.unlock(); // 手动释放锁
// 在 unique_lock 离开作用域时,会自动释放锁同样支持手动操作,加锁和解锁