2.3高性能网络设计专栏-协程设计原理与汇编实现

协程学习概览

重点在哪里?

带着9个问题,看完课程后,以ntyco源码为参照,仿写一遍

1.为什么会要有协程?
2.协程实现过程,原语操作(哪些原语操作)
3.协程如何定义 struct coroutine
4.调度器如何定义, struct scheduler
5.调度器的执行策略
6.posix api做到一致
7.协程的执行流程
8.协程的多核模式
9.协程的性能如何测试

如何用?

不建议把协程作为单独的项目

  1. 协程做为网络库 \(\Rightarrow\) webserver
  2. kv存储 \(\Rightarrow\) redis
  3. 图床 \(\Rightarrow\) 网络层

为什么会要有协程?

a. 同步的编程方式,异步的性能

以DNS服务器为例,使用协程实现的时间更快!
image

image

此外http请求、Redis、Mysql等所有案例都可以类比为所有客户端去请求服务器的数据。
异步的好处:
性能高
异步的缺点:
代码逻辑不好理解,实现代码容易出现嵌套的方式

作业:列举互联网软件产品,哪些地方10个有多个请求同时存在的功能, 如何使用协程如何做。

b.协程如何实现

//异步代码结构
callback(){
  recv();
}
func(){
  send(fd, callback);
}
//备注: callback是fd有数据接收的时候回调

//同步代码结构
func(){
  send();
  recv();
}

setjmp/longjmp

image
image
但是这种方式实现较为麻烦,尤其是在多线程的情况下。

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;
}

image

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

汇编实现

image

三种方式的优缺点
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 *)

posted @ 2026-03-04 11:24  Xiaomostream  阅读(19)  评论(0)    收藏  举报