协程框架nty_co

一、为什么要有协程?

以DNS请求为例子,客户端向服务器发送域名,服务器回复该域名对应得IP地址。

我们想要以同步的编程方式获得异步的性能!!!

 在Linux下,常使用IO多路复用器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 函数。(同步异步的本质是在IO操作的时候的处理方式的不同)

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

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

二、协程的实现原理


前面说到,协程能异步处理事务,这只是看起来而已。协程的异步处理在于对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) {//遍历睡眠集合,将满足条件的加入到 readynty_coroutine *expired = NULL;while ((expired = sleep_tree_expired(sched)) != ) {TAILQ_ADD(&sched->ready, expired);}//遍历等待集合,将满足添加的加入到 readynty_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 的接口(纯C的协程框架)

大致介绍一下协程工作的流程:
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。测试并发连接。
需要做一些配置测试搭建百万并发项目

五、我们的系统代码怎么改成支持协程呢?

    (也就是如何与posix api兼容

NtyCo的hook

如果我们自己写的代码要引入协程,最傻的办法就是一个函数一个函数的改过来,把每个recv改成nty_recv,这样非常耗时耗力,于是hook就起到了非常好的作用。

  我们可以使用hook,帮助我们不用再封装posix api接口取个别的名字的函数,可以直接用和那些posix api接口同名并且不会冲突的函数(recv()、send()等等),并且功能由我们来具体实现。
  hook提供了两个接口;1. dlsym()是针对系统的,系统原始的api。2. dlopen()是针对第三方的库。
void *dlsym(void *handle, const char *symbol); 头文件是#include <dlfcn.h>

我们用dlsym来处理

在使用hook之前,我们一定要定义一个#define _GNU_SOURCE,定义这个

我们才能使用扩展库,如果不定义的话就不能用

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include<mysql/mysql.h>
//
// Created by 68725 on 2022/7/17.
//typedef int (*connect_t)(int, struct sockaddr *, socklen_t);connect_t connect_f;typedef ssize_t (*recv_t)(int, void *buf, size_t, int);recv_t recv_f;typedef ssize_t (*send_t)(int, const void *buf, size_t, int);send_t send_f;typedef ssize_t (*read_t)(int, void *buf, size_t);read_t read_f;typedef ssize_t (*write_t)(int, const void *buf, size_t);write_t write_f;int connect(int fd, struct sockaddr *name, socklen_t len) {printf("in connect\n");return connect_f(fd, name, len);
}ssize_t recv(int fd, void *buf, size_t len, int flags) {printf("in recv\n");return recv_f(fd, buf, len, flags);
}ssize_t send(int fd, const void *buf, size_t len, int flags) {printf("in send\n");return send_f(fd, buf, len, flags);
}
ssize_t read(int fd, void *buf, size_t len) {printf("in read\n");return read_f(fd, buf, len);
}ssize_t write(int fd, const void *buf, size_t len) {printf("in write\n");return write_f(fd, buf, len);
}static int init_hook() {connect_f = dlsym(RTLD_NEXT, "connect");recv_f = dlsym(RTLD_NEXT, "recv");send_f = dlsym(RTLD_NEXT, "send");read_f = dlsym(RTLD_NEXT, "read");write_f = dlsym(RTLD_NEXT, "write");
}void main() {init_hook();MYSQL *m_mysql = mysql_init(NULL);if (!m_mysql) {printf("mysql_init failed\n");return;}if (!mysql_real_connect(m_mysql, "192.168.109.1", "root", "123456", "cdb", 3306, NULL, 0)) {printf("mysql_real_connect failed\n");return;}else {printf("mysql_real_connect success\n");}
}
//gcc -o hook hook.c -lmysqlclient -I /usr/include/mysql/ -ldl

如果跟mysql,redis建立连接进行io操作,但是不去修改它们提供的客户端源码开发包的时候,就会发现连不上去,因为其源码用的是posix api,recv和send。而协程用的是nty_recv()和nty_send()。两者之间没有关联。 

所以解决方法就用hook函数进行替换

 这样,在执行协程之前,我们先利用hook进行系统函数替换,下面是替换的原理图

 当我们替换掉之后,我们的mysql和redis也就支持我们自己定义的函数了,那么mysql在recv和send的时候也就是用的我们协程支持的recv和send等操作了

那怎么测试呢?

测试mysql和redis要测试二大要点

1.增删改查

2.存储过程

六、协程多核模式

解决协程多核的问题有两种方式

  1. 多进程(实现起来容易,对协程代码本身不用去改)
  2. 多线程(复杂,需要对调度器进行加锁)

  那么做多线程对调度器进行加锁,锁放在哪呢?锁放在调度器结构体里面,因为调度器是全局唯一的,那么要锁哪里呢?<取协程,恢复协程>,这里需要加锁。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

英语——歌曲篇——500 Miles(离家五百里)

乡村音乐&#xff08;country music&#xff09;《500 Miles(离家五百里)》以一种怀乡、寻根 的意识&#xff0c;用思念留住时光还有一点哲理的味道&#xff0c;乡村音乐多年以来都不曾淡出大家的视野&#xff0c;确实有值得留恋的情怀。 500 Miles [The Brothers Four离家五…

用前端框架Bootstrap和Django实现用户注册页面

01-新建一个名为“mall_backend”的Project 命令如下&#xff1a; CD E:\Python_project\P_001\myshop-test E: django-admin startproject mall_backend02-新建应用并注册应用 执行下面条命令依次创建需要的应用&#xff1a; CD E:\Python_project\P_001\myshop-test\mall…

【计算机视觉】相机

文章目录 一、原始的相机&#xff1a;针孔相机&#xff08;Pinhole Camera&#xff09;二、针孔相机的数学模型三、真实相机四、透镜的缺陷 我的《计算机视觉》系列参考UC Berkeley的CS180课程&#xff0c;PPT可以在课程主页看到。 成像原理 一、原始的相机&#xff1a;针孔相机…

信息安全与网络安全的关系

前言 说说信息安全与网络安全的关系 如果你对网络安全入门感兴趣&#xff0c;那么你需要的话可以点击这里&#x1f449;【入门&进阶全套282G学习资源包免费分享&#xff01;】 一、包含和被包含的关系 信息安全包括网络安全&#xff0c;信息安全还包括操作系统安全&…

云计算的基本概念

目录 云计算基本概念 什么是云计算 云计算的优势&#xff08;关键特征&#xff09; 云计算发展历程 云计算发展阶段 云计算的三种服务模式 云计算的四类部署模式 云计算的应用 云计算基本概念 什么是云计算 云计算的基本概念 云计算&#xff08;Cloud Computing&…

J2EE项目部署与发布(Linux版本)

目录 一.jdk&tomcat安装 1.jdk的安装 1.2解压对应的安装包 1.3配置环境变量 2.tomcat的安装 二.mysql的安装 三.后端接口部署 后端部署 导入war包 修改端口 开启访问 一.jdk&tomcat安装 1.jdk的安装 登录VMware Workstation Pro 然后连接MobaXterm 将 jd…

BI零售数据分析,告别拖延症,及时掌握一线信息

在日常的零售数据分析中&#xff0c;经常会因为数据量太大&#xff0c;分析指标太多且计算组合多变而导致数据分析报表难产&#xff0c;零售运营决策被迫拖延症。随着BI数据可视化分析技术的发展&#xff0c;智能化、可视化、自助分析的BI数据分析逐渐成熟&#xff0c;形成一套…

精品站长网交易系统源码/虚拟交易网站程序源码/虚拟交易平台带店铺处罚商品处罚

精品站长网交易系统源码&#xff0c;虚拟交易网站程序源码&#xff0c;虚拟交易平台带店铺处罚商品处罚。带申诉功能&#xff0c; 带提现通知功能&#xff0c; 带熊掌号推送功能&#xff0c; 带站内信息功能。 下载地址&#xff1a;https://bbs.csdn.net/topics/617494651

C++学习笔记之四(标准库、标准模板库、vector类)

C 1、C标准库2、C标准模板库2.1、vector2.1.1、vector与array2.1.2、vector与函数对象2.1.3、vector与迭代器2.1.4、vector与算法 1、C标准库 C C C标准库指的是标准程序库( S t a n d a r d Standard Standard L i b a r a y Libaray Libaray)&#xff0c;它定义了十个大类…

如何使用IP归属地查询API加强网络安全

引言 在当今数字化时代&#xff0c;网络安全对于个人和组织来说至关重要。恶意网络活动的威胁不断增加&#xff0c;因此采取有效的措施来加强网络安全至关重要。其中之一是利用IP归属地查询API。这个工具可以为您的网络安全策略提供宝贵的信息&#xff0c;帮助您更好地保护自己…

huggingface离线模式及默认保存路径

T5Tokenizer.from_pretrained()函数会在线下载huggingface上的模型&#xff0c;并自动保存在C盘的.cache路径下&#xff1a; C:\Users\xxxxx\.cache\huggingface\hub\models--google--t5-v1_1-base # 这里xxxxx代表自己电脑用户名huggingface离线下载 以google/t5-v1_1-base为…

win10 下编译ffmpeg3.36.tar.gz

所需工具&#xff1a; win10 ffmpeg3.36.tar.gz。 或其他版本&#xff0c;下载地址&#xff1a;Index of /releases msys2。 下载地址&#xff1a;http://www.msys2.org。 Visual Studio 2017。 1. 安装MSYS MSYS2像是windows下的一个子系统&#xff0c;…

hadoop权威指南第四版

第一部分 HaDOOP基础知识 1.1 面临的问题 存储越来越大&#xff0c;读写跟不上。 并行读多个磁盘。 问题1 磁盘损坏 – 备份数据HDFS 问题2 读取多个磁盘用于分析&#xff0c;数据容易出错 --MR 编程模型 1.2 衍生品 1 在线访问的组件是hbase 。一种使用hdfs底层存储的模型。…

CSS3盒模型

CSS3盒模型规定了网页元素的显示方式&#xff0c;包括大小、边框、边界和补白等概念。2015年4月&#xff0c;W3C的CSS工作组发布了CSS3基本用户接口模块&#xff0c;该模块负责控制与用户接口界面相关效果的呈现方式。 1、盒模型基础 在网页设计中&#xff0c;经常会听到内容…

DSP 开发例程(5): tcp_server

目录 DSP 开发例程(5): tcp_server创建工程源码编辑tcp_echo.chelloWorld.c 调试说明 DSP 开发例程(5): tcp_server 此例程实现在 EVM6678L 开发板上创建 TCP Server进程, 完成计算机与开发板之间的 TCP/IP 通信. 例程源码可从我的 gitee 仓库上克隆或下载. 点击 DSP 开发教程…

组件化npm包打包和使用

背景&#xff1a;本地环境对功能组件提取&#xff0c;开发环境下通过本地路径引用&#xff0c;发布模式下走npm包引用 1、项目下新建packages/HelloWorld文件夹&#xff0c;在此文件夹下运行终端 npm init 新建packages/HelloWorld/index.vue文件 新建packages/HelloWorld/ind…

「完美修复」concrt140.dll丢失的修复方法

concrt140.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2015 Redistributable的一部分。这个文件通常位于Windows系统的System32文件夹中&#xff0c;它提供了一些用于多线程编程的函数和类。当你在运行某些程序时&#xff0c;系统会调用这个文件中的函数来执行…

VR全景图片如何制作?揭秘VR全景图片制作全流程

引言&#xff1a; VR全景图片是一种以全景视角为基础的图片制作技术&#xff0c;能够呈现出更为真实、立体的视觉体验。通过VR全景图片&#xff0c;观众可以360环顾四周&#xff0c;仿佛身临其境&#xff0c;提供了一种全新的感官体验&#xff0c;那么如何制作出令人满意的全景…

数据库-用户权限管理

创建用户&#xff1a; create user testlocalhost identified by 123456; create user 创建用户的固定开头 testlocalhost test为用户名&#xff0c; localhost:新建用户可以在哪些主机上登录&#xff0c;即使可以使用IP地址&#xff0c;网段主机名都可以 identified by 1…

【AIFEM案例分析】药柱随机响应分析

AIFEM是由天洑自主研发的一款通用的智能结构仿真软件&#xff0c;助力用户解决固体结构相关的静力学、动力学、振动、热力学等实际工程问题&#xff0c;软件提供高效的前后处理工具和高精度的有限元求解器&#xff0c;帮助用户快速、深入地评估结构的力学性能&#xff0c;加速产…