【C++20】从0开始自制协程库
文章目录
- 参考
很多人对协程的理解就是在用户态线程把CPU对线程的调度复制了一遍,减少了线程的数量,也就是说在一个线程内完成对协程的调度,不需要线程切换导致上下文切换的开销。但是线程切换是CPU行为,就算你的程序只有一个线程,多个应用一样会切换上下文,所以并不能提升效率。
所以协程必须引入线程池以及epoll的多路复用才能在大部分情况下提升每个线程的运行效率,防止线程空转。
协程本质上是一种用户态的轻量级线程,它允许在一个线程内切换执行多个任务,而不需要进入内核态。尽管协程的设计可以显著减少上下文切换的开销和线程调度的复杂性,但它们并不是万能的。要在大多数情况下提升每个线程的运行效率,并防止线程空转,确实需要引入一些机制,如线程池和 epoll 的多路复用。
为什么需要线程池和 epoll
线程池:
目的:线程池通过预先创建和管理一组线程,减少了线程创建和销毁的开销。线程池中的线程可以重复使用,从而提高系统的吞吐量。
协程与线程池的结合:协程在每个线程上运行,可以充分利用线程池中的每个线程,避免每次任务执行都创建和销毁线程的高成本操作。
防止空转:当没有任务时,线程可以阻塞等待新的任务,从而避免空转。
epoll 多路复用:
目的:epoll 提供了高效的 I/O 事件通知机制,可以监视多个文件描述符,只有当文件描述符准备好进行 I/O 操作时才会通知应用程序,从而避免了轮询所有文件描述符的低效操作。
协程与 epoll 的结合:协程可以与 epoll 结合使用,通过 epoll 进行 I/O 多路复用,协程可以在等待 I/O 操作时挂起,直到 I/O 操作完成后再恢复执行,从而实现非阻塞 I/O 和高效的并发处理。
示例:使用协程、线程池和 epoll
以下是一个简单的示例,展示如何结合使用协程、线程池和 epoll 来实现高效的并发处理。
cpp
复制代码
#include
#include
#include
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#include
// 简单的协程任务类
class CoroutineTask {
public:
struct promise_type {
CoroutineTask get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
CoroutineTask() = default;
};
// 线程池类
class ThreadPool {
public:
ThreadPool(size_t num_threads) {
for (size_t i = 0; i < num_threads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lockstd::mutex lock(this->queue_mutex);
this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty()) return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
}
template <class F>
void enqueue(F&& f) {{std::unique_lock<std::mutex> lock(queue_mutex);tasks.emplace(std::forward<F>(f));}condition.notify_one();
}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread& worker : workers) {worker.join();}
}
private:
std::vectorstd::thread workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop = false;
};
// 设置文件描述符为非阻塞模式
void set_non_blocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
// 协程任务:处理 I/O 事件
CoroutineTask handle_io(int epoll_fd, int fd) {
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
while (true) {co_await std::suspend_always{}; // 挂起协程,等待 I/O 事件char buf[1024];ssize_t n = read(fd, buf, sizeof(buf));if (n > 0) {std::cout << "Read " << n << " bytes from fd " << fd << std::endl;} else if (n == 0) {std::cout << "EOF on fd " << fd << std::endl;close(fd);break;} else if (errno != EAGAIN && errno != EWOULDBLOCK) {std::cerr << "Error reading from fd " << fd << std::endl;close(fd);break;}
}
}
int main() {
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror(“epoll_create1”);
return 1;
}
ThreadPool pool(4);int listen_fd = /* ... 初始化监听 socket ... */;
set_non_blocking(listen_fd);epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listen_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {threads.emplace_back([&] {while (true) {epoll_event events[10];int nfds = epoll_wait(epoll_fd, events, 10, -1);if (nfds == -1) {perror("epoll_wait");return;}for (int n = 0; n < nfds; ++n) {if (events[n].data.fd == listen_fd) {int conn_fd = /* ... 接受新的连接 ... */;set_non_blocking(conn_fd);pool.enqueue([epoll_fd, conn_fd] {handle_io(epoll_fd, conn_fd);});} else {pool.enqueue([fd = events[n].data.fd] {handle_io(epoll_fd, fd);});}}}});
}for (auto& t : threads) {t.join();
}close(epoll_fd);
return 0;
}
代码解释
协程任务类:定义了一个简单的协程任务类 CoroutineTask。
线程池类:定义了一个线程池 ThreadPool,用来管理工作线程。
非阻塞设置函数:set_non_blocking 函数用于将文件描述符设置为非阻塞模式。
协程任务 handle_io:处理 I/O 事件的协程任务,使用 co_await std::suspend_always{} 挂起协程,等待 I/O 事件。
主函数:
创建 epoll 文件描述符。
初始化线程池。
设置监听 socket 为非阻塞模式,并将其添加到 epoll 实例中。
创建多个线程,每个线程循环等待 epoll 事件,并将事件处理任务加入线程池中。
总结
通过结合使用协程、线程池和 epoll,可以显著提高单个线程的运行效率,并防止线程空转。协程在用户态实现轻量级任务切换,线程池管理工作线程,epoll 提供高效的 I/O 事件通知机制,三者结合可以构建高效的并发系统。
参考
- 协程革命】理论篇!扫盲,辟谣一条龙!
- 【C++20】从0开始自制协程库,有手就行(上)
- code
- 【C++20】把epoll封装成协程库,支持异步读写TCP套接字,有手就行(中)
- 【C++20】基于协程的简易HTTP服务器(下)
相关文章:
【C++20】从0开始自制协程库
文章目录 参考 很多人对协程的理解就是在用户态线程把CPU对线程的调度复制了一遍,减少了线程的数量,也就是说在一个线程内完成对协程的调度,不需要线程切换导致上下文切换的开销。但是线程切换是CPU行为,就算你的程序只有一个线程…...
Docker 深度解析:从入门到精通
引言 在当今的软件开发领域,容器化技术已经成为一种趋势。Docker 作为容器化技术的代表,以其轻量级、可移植性和易用性,被广泛应用于各种场景。本文将从 Docker 的基本概念入手,详细介绍 Docker 的安装、基本操作、网络配置、数据…...
[C++] 模板编程-02 类模板
一 类模板 template <class T或者typename T> class 类名 { .......... } 1.1 两种不同的实现 在以下的两种实现中,其实第一种叫做成员函数模板,并不能称为类模板因为这种实现,我们在调用时,并不需要实例化为Product这个类指定指定特定类型。 // 实现1 clas…...
嵌入式C++、STM32、树莓派4B、OpenCV、TensorFlow/Keras深度学习:基于边缘计算的实时异常行为识别
1. 项目概述 随着物联网和人工智能技术的发展,智能家居安全系统越来越受到人们的关注。本项目旨在设计并实现一套基于边缘计算的智能家居安全系统,利用STM32微控制器和树莓派等边缘设备,实时分析摄像头数据,识别异常行为(如入侵、跌倒等),并及时发出警报,提高家庭安全性。 系…...
C++ //练习 15.30 编写你自己的Basket类,用它计算上一个练习中交易记录的总价格。
C Primer(第5版) 练习 15.30 练习 15.30 编写你自己的Basket类,用它计算上一个练习中交易记录的总价格。 环境:Linux Ubuntu(云服务器) 工具:vim 代码块: /********************…...
3个方法快速找回忘记的PDF文件密码
为确保PDF文件的重要信息不轻易外泄,很多人都会给PDF文件设置打开密码,但伴随着时间的推移,让我们忘记了原本设置的密码,但这时,我们又非常急需要打开编辑这份文件,这时我们该怎么办呢?下面小编…...
排序算法:选择排序,golang实现
目录 前言 选择排序 代码示例 1. 算法包 2. 选择排序代码 3. 模拟排序 4. 运行程序 5. 从大到小排序 循环细节 外层循环 内层循环 总结 选择排序的适用场景 1. 数据规模非常小 2. 稳定性不重要 3. 几乎全部数据已排序 4. 教育目的 前言 在实际场景中…...
【测试】博客系统的测试报告
项目背景 个人博客系统采用了 SSM 框架与 Redis 缓存技术的组合 ,为用户提供了一个功能丰富、性能优越的博客平台。 在技术架构上 ,SSM 框架确保了系统的稳定性和可扩展性。Spring 负责管理项目的各种组件 ,Spring MVC 实现了清晰的请求处理…...
PointCLIP: Point Cloud Understanding by CLIP
Abstract 近年来,基于对比视觉语言预训练(CLIP)的零镜头和少镜头学习在二维视觉识别中表现出了令人鼓舞的效果,该方法在开放词汇设置下学习图像与相应文本的匹配。然而,通过大规模二维图像-文本对预训练的CLIP是否可以推广到三维识别&#x…...
搜索(剪枝)
定义: 剪枝,就是减少搜索树的规模、尽早排除搜索树中不必要分支的一种手段。 在深度优先搜索中,有以下几类常见的剪枝方法: 优化搜索顺序排除等效冗余可行性剪枝最优性剪枝记忆化剪枝 例题1:AcWing 167.木棒 题目:…...
python基础知识点
最近系统温习了一遍python基础语法,把自己不熟知的知识点罗列一遍,便于查阅~~ python教程 Python 基础教程 | 菜鸟教程 1、python标识符 以单下划线开头 _foo 的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用 f…...
Android SurfaceFlinger——GraphicBuffer获取内存信息(三十一)
上一篇文章介绍了 GraphicBuffer 初始化的 initWithSize() 函数中的申请内存流程,这里我们看一下另一个比较重要的函数,GraphicBufferMapper. getTransportSize 获取内存信息。该函数通常在需要了解缓冲区的实际内存占用情况时调用,例如在调试内存使用情况或优化性能时。 一…...
基于 SASL/SCRAM 让 Kafka 实现动态授权认证
一、说明 在大数据处理和分析中 Apache Kafka 已经成为了一个核心组件。然而在生产环境中部署 Kafka 时,安全性是一个必须要考虑的重要因素。SASL(简单认证与安全层)和 SCRAM(基于密码的认证机制的盐化挑战响应认证机制ÿ…...
通用多级缓件组件
背景 业界第三方缓存框架一般为redis,本地缓地ehcache或guava,一般通过spring提供的restTemplate操作缓存 然而这样会存在以下问题: 与缓存中间件强耦合需手动整合多级缓存不支持注解数据更新时无法自动刷新缓存存在缓存穿透、缓存击穿、缓…...
MindIE Service服务化集成部署通义千问Qwen模型
一、昇腾开发者平台申请镜像 登录Ascend官网昇腾社区-官网丨昇腾万里 让智能无所不及 二、登录并下载mindie镜像 #登录docker login -u XXX#密码XXX#下载镜像docker pull XXX 三、下载Qwen的镜像 使用wget命令下载Qwen1.5-0.5B-Chat镜像,放在/mnt/Qwen/Qwen1.5-…...
chrome 接口请求等待时间(installed 已停止)过长问题定位
参考: 解决实际项目中stalled时间过久的问题 背景: 测试反馈系统开 6 个标签页后, 反应变的很慢 定位: 看接口请求瀑布流, 已停止时间很长, 后端返回速度很快, 确定是前端的问题 推测是并发请求窗口数量的问题, 屏蔽部分一直 pending 的接口, 发现速度正常了, 搜到上面的参…...
HDialog特殊动画效果
基于HDialog的特殊动画效果实现 业务场景 在开发过程中直接使用HDialog所展现的效果很快,同时不能够与用户所点击位置进行交互,会造成用户的体验观感不够好。因此需要实现一种能够从用户点击按钮位置以可变动画效果所展现的Dialog效果。 工作原理及实…...
基因组挖掘指导天然药物分子的发现-文献精读34
基因组挖掘指导天然药物分子的发现 摘要 天然产物是临床药物的主要来源,也是新药研发过程中先导化合物结构设计和优化的灵感源泉。但传统策略天然药源分子的发现却遭遇了瓶颈,新颖天然产物的数量逐渐无法满足现代药物开发的需求和应对全球多药耐药的威胁…...
hcip学习 DHCP中继
DHCP 中继 在可能收到 DHCP Discover 报文的接口配置 DHCP 中继, 指明 DHCP 服务器的地址,然后将 DHCP 发现报文以单播的形式送到 DHCP 服务器上 DHCP 中继报文的源地址和目标地址怎么确定 1、源地址:收到 Discover 报文的接口地址 2、目…...
[Mysql-函数、索引]
目录 函数: 日期函数 字符串函数 数学函数 聚合函数 索引: 索引分类 慢查询 创建索引 函数: MySQL函数,是一种控制流程函数,属于数据库用语言。 MySQL常见的函数有: 数学函数 用作常规的数学运…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
