学习笔记_协程的实现和原理

学习笔记_协程的实现和原理

ucontext

user context 用户上下文

23 general register 23个通用寄存器

enum
{
      REG_R8 = 0,
    # define REG_R8   REG_R8
      REG_R9,
    # define REG_R9   REG_R9
      REG_R10,
    # define REG_R10  REG_R10
      REG_R11,
    # define REG_R11  REG_R11
      REG_R12,
    # define REG_R12  REG_R12
      REG_R13,
    # define REG_R13  REG_R13
      REG_R14,
    # define REG_R14  REG_R14
      REG_R15,
    # define REG_R15  REG_R15
      REG_RDI,
    # define REG_RDI  REG_RDI
      REG_RSI,
    # define REG_RSI  REG_RSI
      REG_RBP,
    # define REG_RBP  REG_RBP
      REG_RBX,
    # define REG_RBX  REG_RBX
      REG_RDX,
    # define REG_RDX  REG_RDX
      REG_RAX,
    # define REG_RAX  REG_RAX
      REG_RCX,
    # define REG_RCX  REG_RCX
      REG_RSP,
    # define REG_RSP  REG_RSP
      REG_RIP,
    # define REG_RIP  REG_RIP
      REG_EFL,
    # define REG_EFL  REG_EFL
      REG_CSGSFS,   /* Actually short cs, gs, fs, __pad0.  */
    # define REG_CSGSFS REG_CSGSFS
      REG_ERR,
    # define REG_ERR  REG_ERR
      REG_TRAPNO,
    # define REG_TRAPNO REG_TRAPNO
      REG_OLDMASK,
    # define REG_OLDMASK  REG_OLDMASK
      REG_CR2
    # define REG_CR2  REG_CR2
};

主要的结构体

mcontext_t 描述整个处理器状态的上下文
/* Context to describe whole processor state.  */
typedef struct
{
    gregset_t gregs; // 保存23个通用寄存器状态
    fpregset_t fpregs;  // 表示浮点寄存器FPU register的状态指针
    __extension__ unsigned long long __reserved1 [8]; // 保留
} mcontext_t;

ucontext_t 用户上下文
/* Userlevel context.  */
typedef struct ucontext {
    unsigned long int uc_flags;
    struct ucontext* uc_link; // 当前context执行结束后要执行的下一个context
         // 如果uc_link为空,执行完当前context后退出程序
    stack_t uc_stack; // 当前使用的栈信息, 当前context所需stack
    mcontext_t uc_mcontext; // 当前处理器状态, 保存具体的程序执行上下文
    __sigset_t uc_sigmask; // 执行context过程中, 要屏蔽的信号集合, 即信号掩码
    struct _libc_fpstate __fpregs_mem; //表示浮点寄存器FPUregister的状态
} ucontext_t;

/*Alternate, preferred interface.*
typedef struct sigaltstack {
    void* ss_sp;
    int ss_flags;
    size_t ss_size;
} stack_t

主要函数, 成功返回0, 错误返回-1, 并设置errno

int getcontext(ucontext_t *ucp)
将当前context保存到ucp中

int setcontext(const ucontext_t* ucp)
恢复当前context为ucp所指的context, 成功的调用函数不会返回, 
ucp所指的context应该通过getcontext或makecontext获取

void makecontext(ucontext_t* ucp, void(*func)(), int argc, ...)
修改context ucp, 再getcontext获取到context后才可以调用,
可以通过修改ucp中的uc_stack来自定义context使用的堆和堆内存空间大小, 
修改uc_link设置完成后运行的上下文, 
传入func函数来设置context ucp要执行的函数, argc为传入func的参数数量, 
...为传入的参数

int swapcontext(ucontext_t*_restrict_oucp, const ucontext_t* _restrict_ ucp)
保存当前context到oucp中, 然后激活ucp context, 执行成功函数不返回

协程框架

为什么会有协程,解决什么问题?

同步的编程方式,异步的性能,写代码的时候同步,运行的逻辑异步。
异步的性能,将检测IO和读写IO放在不同的线程中
同步的编程方式,麻烦点,性能低,好处是,逻辑简单。
异步的编程方式,麻烦点,免多个线程公用一个fd的现象,这种现象会造成recv的数据乱序和突然被另一边关闭的问题。好处是,性能比较高。
客户端流程
send之后进入 让出 ,进入epoll_wait,判断是否可以recv,否就 切换 到其他的send

// 同步的编程方式,检测IO和读写IO在同一流程里面
fun() {
    while(1) {
        epoll_wait()
        for(;;) {
            recv();
            send();
        }
    }
}
// 异步的编程方式,epoll_wait和recv&send不在同一流程里面
thread_cb() {
    poll();//在异步编程中利用poll对fd进行处理
    recv();
    send();
}
fun() {
    while(1) {
        epoll_wait()
        for(;;) {
            push_other_thread(thread_cb);
        }
    }
}
// 异步的编程方式,利用对event的增删一样不推荐
thread_cb() {
    recv();
    epol_ctl(EPOLL_CTL_ADD,EPOLLIN);
    send();
}
fun() {
    while(1) {
        epoll_wait()
        for(;;) {
            epol_ctl(EPOLL_CTL_DEL,EPOLLIN);
            push_other_thread(thread_cb);
        }
    }
}

// 如何把下面的流程变为异步
epoll_wait()
for(;;) {
    recv()
    parser()
    send()
}
// 变成:io操作-》epoll检测-》io操作 这样子的循环
// 这就是协程的雏形,是在同一个线程里面
while(idx++ < 50) {
    send(fd);
    event->fd = fd;
    event->event = EPOLLIN;
    epoll_ctl(epfd, add, event);
    int nready = epoll_wait(epfd);
    while(i++ < nready) {
        recv();
    }
}

// 协程,yield resume
while(idx++ < 50) {
    send(fd);
    event->fd = fd;
    event->event = EPOLLIN;
    epoll_ctl(epfd, add, event);
    jup label;//切换到epoll_wait yeild
}
label:
int nready = epoll_wait(epfd);
while(i++ < nready) {
    recv();
}
ret;//回到循环  resume

原语 yield, resume,让出,恢复

协程切换相关寄存器

  • X86-64的16个64位寄存器:

    // 为了兼容X86,结构体命令采用的是x86的寄存器名字命名
    typedef struct _nty_cpu_ctx {
    void *esp; //% rsp
    void *ebp; //% rbp
    void *eip;
    void *edi;
    void *esi;
    void *ebx;
    void *r1;
    void *r2;
    void *r3;
    void *r4;
    void *r5;
    } nty_cpu_ctx;

    int _switch(nty_cpu_ctx *new_ctx, nty_cpu_ctx *cur_ctx);
    asm (
    " .text \n"
    " .p2align 4,15 \n"
    “.globl _switch \n”
    “.globl __switch \n”
    “_switch: \n”
    “__switch: \n”
    " movq %rsp, 0(%rsi) # save stack_pointer \n"
    " movq %rbp, 8(%rsi) # save frame_pointer \n"
    " movq (%rsp), %rax # save insn_pointer \n"
    " movq %rax, 16(%rsi) \n"
    " movq %rbx, 24(%rsi) # save rbx,r12-r15 \n"
    " movq %r12, 32(%rsi) \n"
    " movq %r13, 40(%rsi) \n"
    " movq %r14, 48(%rsi) \n"
    " movq %r15, 56(%rsi) \n"
    " movq 56(%rdi), %r15 \n"
    " movq 48(%rdi), %r14 \n"
    " movq 40(%rdi), %r13 # restore rbx,r12-r15 \n"
    " movq 32(%rdi), %r12 \n"
    " movq 24(%rdi), %rbx \n"
    " movq 8(%rdi), %rbp # restore frame_pointer \n"
    " movq 0(%rdi), %rsp # restore stack_pointer \n"
    " movq 16(%rdi), %rax # restore insn_pointer \n"
    " movq %rax, (%rsp) \n"
    " ret \n"
    );

切换

  • goto为什么不能实现?
    goto是在栈内进行切换

  • 实现的3种方法

    • setjmp/longjmp
    • ucontext
    • 汇编的代码
  • 切换流程

    • save,保存寄存器
    • load,加载寄存器
  • switch(new->ctx, cur->ctx);

      #if defined(__x86_64__)
      __asm__ (
      "    .text                                  \n"
      "       .p2align 4,,15                                   \n"
      ".globl _switch                                          \n"
      ".globl __switch                                         \n"
      "_switch:                                                \n"
      "__switch:                                               \n"
      "       movq %rsp, 0(%rsi)      # save stack_pointer     \n"
      "       movq %rbp, 8(%rsi)      # save frame_pointer     \n"
      "       movq (%rsp), %rax       # save insn_pointer      \n"
      "       movq %rax, 16(%rsi)                              \n"
      "       movq %rbx, 24(%rsi)     # save rbx,r12-r15       \n"
      "       movq %r12, 32(%rsi)                              \n"
      "       movq %r13, 40(%rsi)                              \n"
      "       movq %r14, 48(%rsi)                              \n"
      "       movq %r15, 56(%rsi)                              \n"
      "       movq 56(%rdi), %r15                              \n"
      "       movq 48(%rdi), %r14                              \n"
      "       movq 40(%rdi), %r13     # restore rbx,r12-r15    \n"
      "       movq 32(%rdi), %r12                              \n"
      "       movq 24(%rdi), %rbx                              \n"
      "       movq 8(%rdi), %rbp      # restore frame_pointer  \n"
      "       movq 0(%rdi), %rsp      # restore stack_pointer  \n"
      "       movq 16(%rdi), %rax     # restore insn_pointer   \n"
      "       movq %rax, (%rsp)                                \n"
      "       ret                                              \n"
      );
      #endif
    

协程结构体定义

pthread_create(threadid, NULL, func, arg);
// 注意NULL,是线程的属性,用于设置栈的大小
pthread_join(threadi, func_retval);

struct cpu_register_set {
    void *eax;
    void *ebx;
    ...
};
// 其中**last表示
#define queue(name,type)\
struct name {           \
    struct type *first; \
    struct type **last; \
}
#define rbtree_node(name, type)\
struct name {\
    char color;\
    struct type *right;\
    struct type *left;\
    struct type *parent;\
}

struct coroutine {
    struct cpu_register_set *set; // 保存CPU寄存器组
    void *func; // 协程的入口函数
    void *arg; // 入口函数的参数
    void *retval; // 入口函数的返回值
    // 栈存储大括号中,当协程一开始执行的时候,就将stack执行栈指针
    // 全局栈,共享栈,所有的协程共用一个栈,不推荐
    // 独立栈,每个协程分配独立的固定的空间
    // 栈:用于维护函数调用的上下文,包括函数的参数,局部变量等等
    // 栈帧是从栈上分配的一段内存,每次函数调用时,用于存储自动变量。
    // 从物理介质角度看,栈帧是位于esp(栈指针)及ebp(基指针)之间的一块区域。
    // 局部变量等分配均在栈帧上分配,函数结束自动释放。
    void *stack; 
    size_t stack_size;
    // 新建,就绪:队列
    // 等待:红黑树
    // 睡眠:红黑树-->key
    queue(struct coroutine) *ready; // 新建、就绪
    rbtree(struct coroutine) *wait; // 等待
    rbtree(struct coroutine) *sleep; // 睡眠optional
    // 
}
coroutine_create(entry_cb, arg);
coroutine_exec(co) {
    co->value = co->func(co->arg);
}
// 等待协程入口函数返回并获得返回值,这个函数就和pthread_join差不读
int coroutine_join(coid, &ret) {
    // 阻塞
    co = search(coid)
    if(co->ret == NULL) {
        co_wait();
    }
    ret = co->retval;
    return 0;
}

struct scheduler {
    struct scheduler_pos *pos;
    struct coroutine* cur;
    int epfd;
    queue_node *read_set;
    rbtree() *wait_set;
    rbtree() *sleep_set;
}
// 协程的调度策略
struct scheduler_pos {
    struct scheduler_ops *next;
    enset();
    deset();
}

typedef struct _nty_coroutine {
    //private
    nty_cpu_ctx ctx;
    proc_coroutine func;
    void *arg;
    void *data;
    size_t stack_size;
    size_t last_stack_size;
    
    nty_coroutine_status status;
    nty_schedule *sched;

    uint64_t birth;
    uint64_t id;
#if CANCEL_FD_WAIT_UINT64
    int fd;
    unsigned short events;  //POLL_EVENT
#else
    int64_t fd_wait;
#endif
    char funcname[64];
    struct _nty_coroutine *co_join;

    void **co_exit_ptr;
    void *stack;
    void *ebp;
    uint32_t ops;
    uint64_t sleep_usecs;

    RB_ENTRY(_nty_coroutine) sleep_node;
    RB_ENTRY(_nty_coroutine) wait_node;

    LIST_ENTRY(_nty_coroutine) busy_next;

    TAILQ_ENTRY(_nty_coroutine) ready_next;
    TAILQ_ENTRY(_nty_coroutine) defer_next;
    TAILQ_ENTRY(_nty_coroutine) cond_next;

    TAILQ_ENTRY(_nty_coroutine) io_next;
    TAILQ_ENTRY(_nty_coroutine) compute_next;

    struct {
        void *buf;
        size_t nbytes;
        int fd;
        int ret;
        int err;
    } io;

    struct _nty_coroutine_compute_sched *compute_sched;
    int ready_fds;
    struct pollfd *pfds;
    nfds_t nfds;
} nty_coroutine;

调度的策略

调度器如何定义

协程api的实现,hook,将同步io该为异步

socket
bind
listen
accept
send
recv
close
connect

king_accept() {
    int ret = poll(fd);//判断fd是否就绪
    if(ret >0) {
        accept();
    } else {
        epoll_ctl(epfd);
        yield();
    }
}

多核测试

  1. 多线程,CPU亲缘性,需要锁
  2. 多进程,
  3. CPU指令()

如何测试

同步的编程方式,异步的性能,但是不超过异步的性能
100K连接的测试
coroutine:2300-2400ms
reactor:2200ms

其他问题

堆栈是属于进程的,而不是当个函数或者线程
协程会不会造成实时性不足?协程的切换同epoll_wait的切换差不读概念,不会出现实时性的问题
如果eventlist[1024*1024]改为eventlist[1024],但一次触发1500就绪事件的时候,有没有什么方法能够一次性处理1500个?

学习链接

sylar-C++高性能服务器框架
NtyCo-C协程实现

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的数组是一种线性数据结构,它由相同类型的元素组成并按照顺序存储在内存中。Java中的数组可以使用new关键字动态地创建,并且长度是固定的,一旦创建后就不能再改变它的大小。 Java中的ArrayList是一个动态数组,它可以自动扩容以适应元素的添加和删除。ArrayList实际上是基于数组实现的,它内部维护了一个Object类型的数组来存储元素。当ArrayList的容量不足时,它会根据一定的策略自动扩容数组的大小,以容纳更多的元素。 ArrayList的实现原理主要包括以下几个方面: 1. 初始容量和扩容因子:当创建一个ArrayList时,可以指定一个初始容量和一个扩容因子。初始容量指定了ArrayList内部数组的初始大小,扩容因子指定了数组增长的比例。默认情况下,初始容量为10,扩容因子为1.5。 2. 自动扩容:当ArrayList内部数组的容量不足时,它会自动扩容。具体实现是创建一个新的数组,将原数组中的元素复制到新数组中,并将新元素添加到新数组中。 3. 随机访问:由于ArrayList是基于数组实现的,因此它支持随机访问。可以通过索引来快速访问元素,时间复杂度为O(1)。 4. 插入和删除:在ArrayList中插入和删除元素的时间复杂度取决于操作的位置。如果在末尾插入或删除元素,时间复杂度为O(1),否则需要将后面的元素都向后移动,时间复杂度为O(n)。 总之,ArrayList是Java中常用的动态数组,它的实现基于数组,并且支持随机访问、自动扩容等特性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值