【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服务器(下)

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

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

相关文章

windows服务器启动apache失败,提示请通过cmd命令行启动:net start apache

Windows Server 2012 R2服务器突然停止运行apche&#xff0c;启动apache失败&#xff0c;提示请通过cmd命令行启动:net start apache 1.报错截图&#xff1a; 进入服务里输入命令启动也不行&#xff0c;提示由于登录失败而无法启动服务。 2.问题原因&#xff1a; 服务器www用…

Node.js知识点总结

Node.js知识点总结 Node.js其本质和浏览器一样是一个JavaScript运行环境。浏览器的运行环境为V8引擎浏览器内置API&#xff08;BOM、DOM、Canvas);而node.js的运行环境是V8引擎node提供的API(fs、path、http)。它使JavaScript可以编写后端。 基本知识 fs文件系统模块 它提供一…

springboot宠物相亲平台-计算机毕业设计源码16285

目 录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 开发环境及相关技术介绍 2.1 MySQL数据库的介绍 2.2 B/S架构的介绍 2.3 Java语言 2.4 SpringBoot框架 3 宠物相亲平台系统分析 3.1 可行性分析 3.1.1 技术可行性分析 3.1.2 经济…

基于FPGA + Qt + OpenCv的人脸考勤系统

一:界面设计 客户端界面设计: 服务端界面设计: 简介:首先服务端在注册界面先注册人脸,然后客户端界面进行人脸识别,将人脸识别的图像发送给服务端以后,服务端在图像数据库里寻找人脸比对,若有数据就将查询到的个人信息发送给客户端,并在客户端显示,查询界面是用来查…

Ribbon负载均衡与内核原理

什么是Ribbon? 目前主流的负载方案分为两种&#xff1a; 集中式负载均衡&#xff0c;在消费者和服务提供方中间使用独立的代理方式进行负载&#xff0c;有硬件的&#xff08;比如F5&#xff09;&#xff0c;也有软件的&#xff08;Nginx&#xff09;客户端根据自己的请求做负…

开放式耳机哪种性价比高?五大高口碑优质款式耳机直入!

​或许我们的日常生活中充满了噪声&#xff0c;例如马路、地铁还有公交上&#xff0c;嘈杂的声音会影响我们的心情&#xff0c;同时还会损伤我们的耳朵&#xff0c;在嘈杂的环境中&#xff0c;想听歌想煲剧了怎么办&#xff0c;又不想沉浸在自己的世界里&#xff0c;就可以使用…

rk3588s 定制版 USB adb , USB2.0与USB3.0 区别,adb 由typeC 转换到USB3.0(第二部分)

硬件资源&#xff1a; rk3588s 核心板定制的地板 软件资源&#xff1a; 网盘上的 android12 源码 1 硬件上 客户只想使用 type c 接口中的 usb2.0 OTG 。在硬件上&#xff0c;甚至连 CC芯片都没有连接。 关于一些前置的知识。 1 USB2.0 与 USB3.0 的区别。 usb3.0 兼容2.0 …

深入解析:BeanFactory与ApplicationContext的区别

文章目录 摘要1. IoC容器概述1.1 IoC容器的作用 2. BeanFactory2.1 定义和特点2.2 使用场景 3. ApplicationContext3.1 定义和特点3.2 使用场景 4. 核心区别4.1 初始化方式4.2 依赖处理4.3 应用上下文功能4.4 资源类型4.5 事务管理 5. 使用示例5.1 BeanFactory使用示例5.2 Appl…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十九章 平台总线总结回顾

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

EEtrade:现货黄金盈利计算方法

现货黄金交易作为一种极具吸引力的投资方式&#xff0c;其盈利计算涉及多个关键因素&#xff0c;投资者需深入理解这些因素&#xff0c;才能准确评估交易结果&#xff0c;并制定科学的投资策略。 一、现货黄金基本盈利计算&#xff1a; 利润公式&#xff1a; 利润 (收盘价 -…

深入浅出mediasoup—WebRtcTransport

mediasoup 提供了多种 transport&#xff0c;包括 WebRtcTransport、PipeTransport、DirectTransport、PlainTransport 等&#xff0c;用来实现不同目的和场景的媒体通信。WebRtcTransport 是 mediasoup 实现与 WebRTC 客户端进行媒体通信的对象&#xff0c;是 mediasoup 最重要…

DNS(域名解析协议)

1. DNS工作原理 TCP / IP提供了通过IP地址来连接到设备的功能&#xff0c;但对用户来讲&#xff0c;记住某台设备的IP地址是相当困难的&#xff0c;因此专门设计了一种字符串形式的主机命名机制&#xff0c;这些主机名与IP地址相对应。在IP地址与主机名之间需要有一种转换和查询…

如何学习Airflow:糙快猛的大数据之路(附思维导图)

什么是Airflow? 在开始之前,让我们先简单了解一下Airflow是什么。Apache Airflow是一个开源的工作流管理平台。它允许你以代码的方式定义、调度和监控复杂的数据处理管道。 想象一下,你有一系列需要按特定顺序执行的任务,而且这些任务之间还有依赖关系,Airflow就是为解决这…

SpringBoot自动配置(面试重点)

自动配置是指&#xff1a; 自动配置是指在应用程序启动时&#xff0c;SpringBoot根据classpath路径下的jar包自动配置应用程序所需的一系列bean和组件&#xff0c;从而减少开发者的配置工作&#xff0c;提高开发效率。 一&#xff1a;Condition Condition是spring4.0之后添加…

后端开发面试题6(附答案)

前言 在下首语言是golang,所以会用他作为示例。 原文参见 @arialdomartini的: Back-End Developer Interview Questions 分布式系统相关问题 1. 怎么测试一个分布式系统? 测试分布式系统是一项复杂且具有挑战性的任务,因为它涉及到多个组件在不同的网络环境和硬件设施上的…

江科大/江协科技 STM32学习笔记P8

文章目录 按键控制LED&光敏传感器控制蜂鸣器1、按键控制LED.cKey.c读取GPIO端口的库函数main.c 2、光敏传感器控制蜂鸣器Buzzer.cLightSensor.cmain.c 按键控制LED&光敏传感器控制蜂鸣器 1、按键控制 用LED.c和LED.h两个文件封装LED的驱动程序&#xff0c;LED.c存放驱…

linux离线安装mysql8(单机版)

文章目录 一、检查服务器是否有残留mysql资源&#xff0c;有的话就全删除1.1、查询mysql已安装的相关依赖&#xff1a;1.2、查找含有MySQL的目录 二、安装2.1、上传mysql安装包到文件夹下并解压2.2、移动及重命名2.3、mysql用户2.4、配置mysql所需的my.cnf文件2.5、给my.cnf配置…

JVM之经典垃圾回收器

1.垃圾回收器的分类 处理范围划分&#xff1a; 新生代垃圾回收器&#xff1a;serial、parNew、parallel scavenge&#xff1b; 老年代垃圾回收器&#xff1a;serial Old、parallel Old、CMS&#xff1b; 整堆收集器&#xff1a;G1、ZGC&#xff1b; 2.Serial GC Serial是单…

java单元测试:Mockito常用技巧

Mockito是Java中最流行的Mock框架之一&#xff0c;主要用于创建和配置模拟对象&#xff08;Mock&#xff09;&#xff0c;以测试代码的行为。Mockito使得单元测试更加简单和可控&#xff0c;特别是在需要隔离外部依赖的情况下。 1. Mockito简介 1.1 什么是Mockito Mockito是一个…

QGC二次开发入门教程(一):课程大纲

文章目录 前言一、课程大纲二、修改软件名称三、修改软件图标四、官方QGC中文版BUG修复五、汉化六、修改商标七、添加信号-槽八、添加QML和C交互九、MAVLINK的解析与发送十、换地图十一、添加自定义mavlink消息十二、在主工具栏添加一个自定义图标十三、解析自定义mavlink数据并…