2.3高性能网络设计专栏-协程设计原理与汇编实现
协程学习概览
重点在哪里?
带着9个问题,看完课程后,以ntyco源码为参照,仿写一遍
1.为什么会要有协程?
2.协程实现过程,原语操作(哪些原语操作)
3.协程如何定义 struct coroutine
4.调度器如何定义, struct scheduler
5.调度器的执行策略
6.posix api做到一致
7.协程的执行流程
8.协程的多核模式
9.协程的性能如何测试
如何用?
不建议把协程作为单独的项目
- 协程做为网络库 \(\Rightarrow\) webserver
- kv存储 \(\Rightarrow\) redis
- 图床 \(\Rightarrow\) 网络层
为什么会要有协程?
a. 同步的编程方式,异步的性能
以DNS服务器为例,使用协程实现的时间更快!


此外http请求、Redis、Mysql等所有案例都可以类比为所有客户端去请求服务器的数据。
异步的好处:
性能高
异步的缺点:
代码逻辑不好理解,实现代码容易出现嵌套的方式
作业:列举互联网软件产品,哪些地方10个有多个请求同时存在的功能, 如何使用协程如何做。
b.协程如何实现
//异步代码结构
callback(){
recv();
}
func(){
send(fd, callback);
}
//备注: callback是fd有数据接收的时候回调
//同步代码结构
func(){
send();
recv();
}
setjmp/longjmp


但是这种方式实现较为麻烦,尤其是在多线程的情况下。
ucontext
#include <ucontext.h>
#include <stdio.h>
ucontext_t ctx[3];
ucontext_t main_ctx;
int count = 0;
//coroutine1
void func1(void) {
while(count ++ < 20) {
printf("1\n");
swapcontext(&ctx[0], &main_ctx);
printf("4\n");
}
}
//coroutine2
void func2(void) {
while(count ++ < 20) {
printf("2\n");
swapcontext(&ctx[1], &main_ctx);
printf("5\n");
}
}
//coroutine3
void func3(void) {
while(count ++ < 20) {
printf("3\n");
swapcontext(&ctx[2], &main_ctx);
printf("6\n");
}
}
// 调度器 schedule
int main() {
char stack1[2048] = {0};
char stack2[2048] = {0};
char stack3[2048] = {0};
getcontext(&ctx[0]);
ctx[0].uc_stack.ss_sp = stack1;
ctx[0].uc_stack.ss_size = sizeof(stack1);
ctx[0].uc_link = &main_ctx;
makecontext(&ctx[0], func1, 0);
getcontext(&ctx[1]);
ctx[1].uc_stack.ss_sp = stack2;
ctx[1].uc_stack.ss_size = sizeof(stack1);
ctx[1].uc_link = &main_ctx;
makecontext(&ctx[1], func2, 0);
getcontext(&ctx[2]);
ctx[2].uc_stack.ss_sp = stack3;
ctx[2].uc_stack.ss_size = sizeof(stack3);
ctx[2].uc_link = &main_ctx;
makecontext(&ctx[2], func3, 0);
printf("swapcontext\n");
while(count <= 30) { //scheduler,调度器策略,切换协程的规则,需要根据实际业务进行更改
swapcontext(&main_ctx, &ctx[count%3]);
}
printf("\n");
return 0;
}

switch是底层的动作,配合着不同的业务进行yield(让出运行)、resume(恢复运行)。
遇到io等待切换,直到就绪恢复
io操作没有就绪,则切换 yield
io操作就绪,则恢复 resume
汇编实现

三种方式的优缺点
setjmp、longjmp实现比较复杂,但是跨平台性最好,其使用的是libc的接口
ucontext的代码是最容易的,但是适合在Linux平台上,其他平台支持行不好
汇编的效率最高,但受限于cpu的结构(ARM\Intel x86\ ...)需要根据不同芯片写相应的汇编语言
把协程io操作, recv/send/read/write \(\rightarrow\) hook
协程如何定义,用协程借鉴网络io处理的开源框架
struct coroutine {
int fd;
ucontext_t ctx;
};
typedef void *(*coroutine_entry)(void *)

浙公网安备 33010602011771号