协程框架NtyCo的实现

news2024/10/7 14:31:09

一、为什么需要协程?

讨论协程之前,我们需要先了解同步和异步。以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 操作,此时进入到睡眠状态集合。

就绪:都准备好了,就等着执行。就绪(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。测试并发连接。
(设置了到达一百万自动退出,理论上可以达到,本人电脑太弱鸡,跑到70多万就崩了)
需要做一些配置测试搭建百万并发项目
在这里插入图片描述

五、代码地址

Github:NtyCo


注:本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务器课程链接 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/904119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【unity数据持久化】XML数据管理器知识点

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【周末闲谈】关于“数据库”你又知道多少?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 系列目录 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 系列目录前言数据库数据库的五大特点数据库介绍数据库管理系统&a…

Linux命令200例:head用于显示文件的开头部分(常用)

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌。CSDN专家博主&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &…

2023-08-20 LeetCode每日一题(判断根结点是否等于子结点之和)

2023-08-20每日一题 一、题目编号 判断根结点是否等于子结点之和二、题目链接 点击跳转到题目位置 三、题目描述 给你一个 二叉树 的根结点 root&#xff0c;该二叉树由恰好 3 个结点组成&#xff1a;根结点、左子结点和右子结点。 如果根结点值等于两个子结点值之和&…

.fargo后缀勒索病毒|勒索病毒解密恢复|fargo勒索病毒解密|勒索病毒解密恢复|数据库恢复

fargo勒索病毒概述&#xff0c;fargo勒索病毒解密恢复及日常防护建议 目录&#xff1a; fargo勒索病毒介绍感染fargo勒索病毒后的表现fargo勒索病毒的感染形式如何恢复.fargo后缀勒索病毒fargo勒索病毒日常防护建议 简介&#xff1a; 河北某有限公司的财务系统&#xff0c;由…

下线40万辆,欧拉汽车推出2023款好猫尊荣型和GT木兰版

欧拉汽车是中国新能源汽车制造商&#xff0c;成立于2018年。截至目前&#xff0c;已经下线了40万辆整车&#xff0c;可见其在市场的影响力和生产实力。为了庆祝这一里程碑&#xff0c;欧拉汽车推出了品牌书《欧拉将爱进行到底》&#xff0c;在其中讲述了欧拉汽车的发展历程和未…

2000-2021年全国各省份农业碳排放数据(原始数据+测算过程+碳排放的测算结果)

2000-2021年全国各省份农业碳排放数据&#xff08;原始数据测算过程碳排放的测算结果&#xff09; 1、时间&#xff1a;2000-2021年 2、范围&#xff1a;全国31省市 3、指标&#xff1a;化肥使用量(万吨)、塑料薄膜使用量(吨)、农用柴油使用量&#xff08;万吨&#xff09;、…

数据结构 - 语句的频度和时间复杂度

一、语句频度&#xff1a; 算法的运行时间 Σ每条语句的执行次数X该语句执行一次所需的时间每条语句的执行次数&#xff0c;也称为&#xff1a;语句的频度结合上面两点&#xff0c;可知&#xff1a;算法的运行时间 Σ每条语句的频度X该语句执行一次所需的时间 二、语句执行…

【产品规划】功能需求说明书概述

文章目录 1、瀑布流方法论简介2、产品需求文档&#xff08;PRD&#xff09;简介3、产品需求文档的基本要素4、编写产品需求文档5、优秀产品需求文档的特点6、与产品需求文档相似的其他文档 1、瀑布流方法论简介 2、产品需求文档&#xff08;PRD&#xff09;简介 3、产品需求文档…

【汇编语言】使用DS和[address]实现字的传送

文章目录 要解决的问题&#xff1a;CPU从内存单元中读取数据字的传送 要解决的问题&#xff1a;CPU从内存单元中读取数据 1、要求&#xff1a;CPU要读取一个内存单元时&#xff0c;必须先给出这个内存单元的地址&#xff1b; 2、原理&#xff1a;8086设备中&#xff0c;内存地…

Kotlin开发笔记:函数式编程

Kotlin开发笔记&#xff1a;函数式编程 什么是函数式编程 简单来说&#xff0c;我们之前接触到的编程的主流就是命令式编程&#xff0c;我们需要告诉计算机做什么和如何做。而函数式编程的意思就是我们只需要告诉计算机我们想做什么&#xff0c;计算机会帮助我们实现如何做。我…

ubuntu20搭建环境使用的一下指令

1.更新源 sudo vim etc/apt/sources.listdeb http://mirrors.aliyun.com/ubuntu/ xenial main deb-src http://mirrors.aliyun.com/ubuntu/ xenial maindeb http://mirrors.aliyun.com/ubuntu/ xenial-updates main deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates…

小素数,大智慧

小素数&#xff0c;大智慧 定义判断方法方法1方法2方法3方法4方法5方法6方法7 定义 素数&#xff08;质数&#xff09;&#xff1a;在大于 1 的自然数中&#xff0c;只有 1 和该数本身两个因数的数 素数&#xff08;质数&#xff09;&#xff1a;在大于1的自然数中&#xff0c;…

No114.精选前端面试题,享受每天的挑战和学习

文章目录 vue3中的ref、toRef、toRefs说明下TS的优缺点说下函数式组件说下函数式编程 vue3中的ref、toRef、toRefs 下面是对Vue 3中的ref、toRef和toRefs进行比较的表格&#xff1a; reftoReftoRefs参数类型值类型或引用类型响应式对象响应式对象返回值Ref 对象Ref 对象响应式…

简单认识Docker的资源控制

文章目录 一、CPU资源限制1.设置CPU使用率上限2.设置CPU资源占用比&#xff08;设置多个容器才有效&#xff09;3.设置容器与CPU绑核 二、内存资源限制三、对磁盘I/O配额的限制 一、CPU资源限制 1.设置CPU使用率上限 Linux通过CFS&#xff08;Completely Fair Scheduler&#…

【简单认识Docker基本管理】

文章目录 一、Docker概述1、定义2.容器化流行的原因3.Docker和虚拟机的区别4.Docker核心概念 二、安装docker三、镜像管理1.搜索镜像2.下载&#xff08;拉取&#xff09;镜像3.查看已下载镜像4.查看镜像详细信息5.修改镜像标签6.删除镜像7.导出镜像文件和拉取本地镜像文件8.上传…

如何在Linux系统上搭建自己的FRP内网穿透

前言 我有一个1核1G的服务器有公网IP但是这个1核1G的服务器太垃圾了,几乎什么都跑不起来,不过网速还行,那我本地还有一个物理主机是一个4核4G的,那我就可以把这台主机安装上linux系统当成一个服务器来使用,然后把网络代理到公网IP上.使用内网穿透这篇文章也就出现了. FRP简介 F…

服务器CPU飚高排查

排查思路 当正在运行的Java服务导致服务器的CPU突然飙高时&#xff0c;我们该如何排查定位到哪个接口的哪行代码导致CPU飙高的问题呢&#xff1f;我主要提供两个方案&#xff1a; jstackarthas 准备工作 代码准备 现在需要准备一段可以让服务器CPU飙高的代码以及把代码部署…

鑫达惠购系统APP开发的功能架构介绍

鑫达惠购是一款新电商模式的购物分销系统&#xff0c;基于分销裂变的商业价值行为&#xff0c;快速地分享邀请用户注册。这个系统的模式有个特别的亮点&#xff0c;基于全网公排的模式快速推动用户在商城上的购买活动。 鑫达惠购客户端系统功能 包含的功能有&#xff1a;商城模…

memset の 那些事儿 (C++)

如果你在编程时开了一个数组 int a[100010] 这是后你想把他全部赋值为一个很大的值&#xff08;可能你用它来取min&#xff09; 这时候&#xff0c;应该这样写 for (int i 0; i < 100010; i ) a[i] 0x3f3f3f3f //0x3f3f3f3f 是一个比较接近int_max的一个数&#xff0…