协程框架NtyCo的实现

先看一下前置知识协程设计原理

一、为什么需要协程?

讨论协程之前,我们需要先了解同步和异步。以epoll多路复用器为例子,其主循环框架如下:

while (1){
    int nready = epoll_wait(epfd, events, EVENT_SIZE, -1);

    int i=0;
    for (i=0; i<nready; i++){

        int sockfd = events[i].data.fd;
        if (sockfd == listenfd){

            int connfd = accept(listenfd, addr, &addr_len);
            
            setnonblock(connfd); //置为非阻塞

            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = connfd;
            epoll_ctl(epfd, EPOLL_CTL_ADD,connfd,&ev);
        }else{
            handel(sockfd); //进行读写操作
        }
    }

}

在通过 accept 建立服务端与客户端的连接之后,需要行读写操作,也就是 handel 函数。根据同步和异步,有两种不同的处理方式。

同步的处理方式
在这里插入图片描述
异步的处理方式
在这里插入图片描述
可见,同步和异步主要区别在于对于 handle 函数的处理。同步在需要等待 handle 函数处理完成,主循环才能继续执行,阻塞了 epoll_wait。而异步是单独为 handle 函数创建一个线程异步处理,主循环不需要等待 handle 函数。

但是问题在于线程的创建、销毁,十分消耗资源。面对来自客户端的数百万连接,每一条都创建线程,很容易把服务器干崩溃。

因此就有了协程,在一个线程里面创建多个协程,共享一个线程的资源,但又能异步(看起来)处理事务。

二、协程的实现原理

前面说到,协程能异步处理事务,这只是看起来而已。协程的异步处理在于对CPU的调度,即需要的时候切入获取CPU操作权,不需要的时候让出CPU操作权。
在这里插入图片描述
这边涉及到以下几个问题:
1、切换的时候怎么做到跟切换前一致?
2、有协程1、协程2、协程3,……,怎么决定由那个协程执行?

首先第一个问题,就是协程切换前后需要进行上下文切换。有汇编、ucontext、longjmp / setjmp。当然,汇编效果最快。

其次第二个问题,协程是一种用户态的轻量级线程,协程的调度完全由用户控制。也就是说,由我们自定义的调度器管理。
在讲调度规则之前,我们需要先了解一下协程创建后会有哪些状态:
1、新创建的协程,创建完成后,加入到就绪集合,等待调度器的调度;
2、协程在运行完成后,进行 IO 操作,此时 IO 并未准备好,进入等待状态集合;
3、IO 准备就绪,协程开始运行,后续进行 sleep 操作,此时进入到睡眠状态集合。

在这里插入图片描述
在协程的上下文 IO 异步操作(nty_recv,nty_send)函数,步骤如下:
1)将 sockfd 添加到 epoll 管理中。
2)进行上下文环境切换,由协程上下文 yield 到调度器的上下文。
3)调度器获取下一个协程上下文。Resume 新的协程

IO 异步操作的上下文切换的时序图如下:
在这里插入图片描述

就绪:都准备好了,就等着执行。就绪(ready)集合并不没有设置优先级的选型,所有在协程优先级一致,所以可以使用队列来存储就绪的协程,简称为就绪队列

等待:没准备好,比如IO操作的recv,信息还没来,recv就还没准备好。等待(wait)集合,其功能是在等待 IO 准备就绪,等待 IO 也是有时长的,所以等待(wait)集合采用红黑树的来存储,简称等待树(wait_tree)

睡眠:指协程主动挂起,等待某个时间后再恢复执行。比如等待IO我们可以设置一个时间,时间内还是没触发,那就算过期超时了。睡眠(sleep)集合需要按照睡眠时长进行排序,采用红黑树来存储,简称睡眠树(sleep_tree)红黑树在工程实用为<key, value>, key 为睡眠时长,value 为对应的协程结点。

因此,基于以上,协程如何被调度?有两种
1、 生产者消费者模式
在这里插入图片描述

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

2、多状态运行
在这里插入图片描述

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

三、NtyCo 的接口

在这里插入图片描述
大致介绍一下协程工作的流程:
1、为accept事件创建一个协程co1,并注册监听事件到co1的epoll,加入等待队列,然后yield,让出CPU控制权
2、为recv事件创建一个协程co2,并注册监听事件到co2的epoll,加入等待队列,然后yield,让出CPU控制权
3、为send事件创建一个协程co3,并注册监听事件到co3的epoll,加入等待队列,然后yield,让出CPU控制权
(以上设置默认睡眠时间,同步加入睡眠队列)
(调度器接手)
4、遍历睡眠集合,使用 resume 恢复过期协程 expired 的协程运行权
5、遍历就绪集合,使用 resume 恢复 ready 的协程运行权
6、遍历等待集合,使用 resume 恢复 wait 的协程运行权
在这里插入图片描述

四、测试结果

4台Ubuntu虚拟机,其中一台服务端4核12G,另外三台1核4G。测试并发连接。
需要做一些配置测试搭建百万并发项目
在这里插入图片描述

五、代码地址

Github:NtyCo

京东数据库设计 Revised on November 25, 2020 Revised on November 25, 2020 京东数据库设计全文共118页,当前为第1页。京东数据库设计 京东数据库设计全文共118页,当前为第1页。 Table of Contents 京东数据库设计全文共118页,当前为第2页。The 'Table of Contents' field needs to be updated! 京东数据库设计全文共118页,当前为第2页。 京东数据库设计全文共118页,当前为第3页。表的清单 京东数据库设计全文共118页,当前为第3页。 名称 注释 account_info中信账户管理 中信账户管理 activity_record活动记录信息 活动记录信息 activity_statements 活动结算表 活动结算表 bace_type bank_settle_detail 第三方支付结算excel表 第三方支付结算excel表 base_consulting_sms咨询表 咨询表 base_dictionary 物流信息表 物流信息表 base_jms_listener消息记录表 消息记录表 base_msg_record基础信息记录 基础信息记录 base_send_message TDK设置表 TDK设置表 base_sms_config基础邮件配置 基础邮件配置 base_tdk_config基础信息TDK设置 基础信息TDK设置 base_user_favorite用户收藏 base_website_message基础网站消息 基础网站消息 central_purchasing_activites_details集采详情表 集采详情表 central_purchasing_activites集采活动表 集采活动表 central_purchasing_enterprise参与集采单位信息表 参与集采单位信息表 central_purchasing_ref_enterprise集采商品与集采单位关联表 集采商品与集采单位关联表 central_purchasing_ref_order集采订单关联表 集采订单关联表 citic_pay_journal 中信支付记录数据交互表 中信支付记录数据交互表 company_pay_job 存放企业用户自动支付的记录表 存放企业用户自动支付的记录表 complain 仲裁信息表 仲裁信息表 contract_info 协议表 协议表 contract_mat 协议明细表 协议明细表 contract_order 协议订单表 协议订单表 contract_payment_term 协议付款合同表 协议付款合同表 contract_url_show 协议附件地址表 协议附件地址表 coupon_info优惠券主表 优惠券主表 coupon_user优惠券与用户的关联表 优惠券和所领此优惠券的用户的关联表 coupon_using_range优惠券使用范围表 优惠券使用范围表 date_dic 静态日期表 静态日期表(广告统计使用) delivery 快递信息表 快递信息表 delivery_addressbase交货地址 delivery_express_company交付快递公司 delivery_fee_info交付费用信息 delivery_info交付信息 delivery_status订单配送状态表 订单配送状态表 原注释:发货状态表(扫描状态用) delivery_useful_address_info用户地址信息 factorage_journal 第三方支付的手续费记录表 第三方支付的手续费记录表 field_certification_attachment认证附件 field_certification认证信息 field_identification_audi实地认证审核表 实地认证审核表 field_identification_picture实地认证图片表 实地认证图片表 finance_account_info 金融帐号配置表 金融帐号配置表 finance_withdraw_apply提现申请 finance_withdraw_record提现记录 inquiry_info 询价表 询价表 inquiry_mat 询价明细表 询价明细表 inquiry_order 询价订单表 询价订单表 integral_config 积分配置表 积分配置表 invoice 发票表 发票表 invoice_pic 发票对应图片表 发票对应图片表 item_attr_value_item(属性属性值和商品关系表) 属性属性值和商品关系表 item_a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闲谈社

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值