网络IO解决方案 — 协程框架的实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络IO解决方案 — 协程框架的实现相关的知识,希望对你有一定的参考价值。

协程这个概念很久了,好多程序员是实现过这个组件的,网上关于协程的文章,博客,论坛都是汗牛充栋,在知乎,github上面也有很多大牛写了关于协程的心得体会。突发奇想,我也来实现一个这样的组件,并测试了一下性能。借鉴了很多大牛的思想,阅读了很多大牛的代码。于是把整个思考过程写下来。实现代码 https://github.com/wangbojing/NtyCo

代码简单易读,如果在你的项目中,NtyCo能够为你解决些许工程问题,那就荣幸之至。


下面将部分的NtyCo的代码贴出来。


NtyCo 支持多核多进程。

int process_bind(void) {

	int num = sysconf(_SC_NPROCESSORS_CONF);

	pid_t self_id = syscall(__NR_gettid);
	printf("selfid --> %d
", self_id);

	cpu_set_t mask;

	CPU_ZERO(&mask);
	CPU_SET(self_id % num, &mask);

	sched_setaffinity(0, sizeof(mask), &mask);

	mulcore_entry(9096 + (self_id % num) * 10);

}


NtyCo 上下文切换

首先来回顾一下x86_64寄存器的相关知识。x86_64 的寄存器有1664位寄存器,分别是:%rax, %rbx, %rcx, %esi, %edi, %rbp, %rsp, %r8, %r9, %r10, %r11, %r12,

%r13, %r14, %r15

%rax 作为函数返回值使用的。

%rsp 栈指针寄存器,指向栈顶

%rdi, %rsi, %rdx, %rcx, %r8, %r9 用作函数参数,依次对应第1参数,第2参数。。。

%rbx, %rbp, %r12, %r13, %r14, %r15 用作数据存储,遵循调用者使用规则,换句话说,就是随便用。调用子函数之前要备份它,以防它被修改

%r10, %r11 用作数据存储,就是使用前要先保存原值。

 

上下文切换,就是将CPU的寄存器暂时保存,再将即将运行的协程的上下文寄存器,分别mov到相对应的寄存器上。此时上下文完成切换。如下图所示:


技术分享图片



代码如下

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


协程的调度器

调度器的实现,有两种方案,一种是生产者消费者模式,另一种多状态运行。

技术分享图片


逻辑代码如下:

while (1) {
 
        //遍历睡眠集合,将满足条件的加入到ready
        nty_coroutine *expired = NULL;
        while ((expired = sleep_tree_expired(sched)) != ) {
            TAILQ_ADD(&sched->ready, expired);
        }
 
        //遍历等待集合,将满足添加的加入到ready
        nty_coroutine *wait = NULL;
        int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1);
        for (i = 0;i < nready;i ++) {
            wait = wait_tree_search(events[i].data.fd);
            TAILQ_ADD(&sched->ready, wait);
        }
 
        // 使用resume回复ready的协程运行权
        while (!TAILQ_EMPTY(&sched->ready)) {
            nty_coroutine *ready = TAILQ_POP(sched->ready);
            resume(ready);
        }
    }


多状态运行

技术分享图片

实现逻辑代码如下:

while (1) {
 
        //遍历睡眠集合,使用resume恢复expired的协程运行权
        nty_coroutine *expired = NULL;
        while ((expired = sleep_tree_expired(sched)) != ) {
            resume(expired);
        }
 
        //遍历等待集合,使用resume恢复wait的协程运行权
        nty_coroutine *wait = NULL;
        int nready = epoll_wait(sched->epfd, events, EVENT_MAX, 1);
        for (i = 0;i < nready;i ++) {
            wait = wait_tree_search(events[i].data.fd);
            resume(wait);
        }
 
        // 使用resume恢复ready的协程运行权
        while (!TAILQ_EMPTY(sched->ready)) {
            nty_coroutine *ready = TAILQ_POP(sched->ready);
            resume(ready);
        }


性能测试

测试环境:4台VMWare 虚拟机

1台服务器 6G内存,4核CPU

3台客户端 2G内存,2核CPU

操作系统:ubuntu 14.04

服务器端测试代码:https://github.com/wangbojing/NtyCo

客户端测试代码:https://github.com/wangbojing/c1000k_test/blob/master/client_mutlport_epoll.c

 

按照每一个连接启动一个协程来测试。协程启动数量能够达70W无异常。



技术分享图片


技术分享图片



以上是关于网络IO解决方案 — 协程框架的实现的主要内容,如果未能解决你的问题,请参考以下文章

Python黑魔法 --- 异步IO( asyncio) 协程

Python黑魔法 --- 异步IO( asyncio) 协程

IO多路复用, 基于IO多路复用+socket实现并发请求(一个线程100个请求), 协程

c语言实现的协程

网络编程进阶:并发编程之协程IO模型

网络编程之协程与池