从零开始手写一个协程库(一)
引言从本篇文章开始我会逐一介绍协程库的一些重要代码和知识点~~~什么是协程库简单来说就是一个超级轻量级的线程这个线程还是用户态线程。那么我们为什么要实现协程库呢在高并发的环境下如果因为一个任务的阻塞而无法执行其他任务会导致整个系统的死机。而协程就帮助了这个点可以在阻塞的时候去执行其他的任务resume 恢复yeild 暂停API的简介对于项目里面的四个主要的API进行介绍一下重点getcontextucontext_t *ucp这个是获取当前的上下文放到ucp中具体就是直到CPU的状态sp指针寄存器等等setcontextconst ucontext_t *ucp这个是设置当前的上下文把ucp指向的地址里面的内容取出来设置为当前要执行的代码和上面那个不同的是我们要执行代码必须要把上下文交给CPU让CPU去执行你的代码。当然这个上下文也就是这个ucp来源有两个一个是getcontext那就继续执行这个调用相当于再执行一遍之前的代码还有一个是makecontext这个我们会先执行第二个参数如果第二个参数返回则恢复makecontext第一个参数指向的上下文第一个参数指向的上下文context_t中指向的uc_link。如果uc_link为NULL则线程退出makecontext(ucontext_t *ucp, void (*func)(), int argc, ...这个是创建一个新的上下文而创建一个上下文需要的是当前的环境一些信号量啥的所以我们的第一个参数就是传递一个原来的上下文指针这个样子我们就不需要关心环境问题了所以我们必须要提前调用getcontext调用之后因为这是一个新的上下文所以要新的栈所以我们需要新的空间去存储这个栈规定栈的大小还要交代uc_link也就是这个函数结束之后我们应该跳转到哪个函数而这一切都是在getcontext之后因为如果在之前那么CPU会记录下我们现在使用的栈和SP指针可是我们现在运行的代码还是原来的上下文也就是说我们直接把我们辛辛苦苦改的新值用旧值覆盖了。但是这个函数不是执行函数这个只是创建了一个上下文swapcontextucontext_t *oucp, ucontext_t *ucp这是一个执行函数保存当前上下文到oucp结构体中然后激活upc上下文#include ucontext.h #include stdio.h void func1(void * arg) { puts(1); puts(11); puts(111); puts(1111); } void context_test() { char stack[1024*128]; ucontext_t child,main; getcontext(child); //获取当前上下文 child.uc_stack.ss_sp stack;//指定栈空间 child.uc_stack.ss_size sizeof(stack);//指定栈空间大小 child.uc_stack.ss_flags 0; child.uc_link main;//设置后继上下文 makecontext(child,(void (*)(void))func1,0);//修改上下文指向func1函数 swapcontext(main,child);//切换到child上下文保存当前上下文到main puts(main);//如果设置了后继上下文func1函数指向完后会返回此处 } int main() { context_test(); return 0; }为了大家更加方便的理解我们这里还抛出一个小问题Hello World这里是不可以被打印出来的原因就是makecontext已经修改了上下文导致setcontext调用完之后发现下一个指向的函数被设定为NULL那么不会返回到之前的地方继续执行了#include stdio.h #include ucontext.h void func1() { puts(In func1); } int main() { ucontext_t context; getcontext(context); context.uc_stack.ss_sp malloc(8192); context.uc_stack.ss_size 8192; context.uc_link NULL; makecontext(context, func1, 0); setcontext(context); puts(Hello World); return 0; }前置知识class Fiber : public std::enable_shared_from_thisFiber这个代码是让Fiber这个类继承了public std::enable_shared_from_this 大类其内部的主要就是给这个fiber类塞进去一个weak_ptr指针这个指针当这个类被调用shared_from_this()函数的时候让weak_ptr执行lock就会让其引用计数1。不要小看这个1。我们举一个简单的例子一个协程在运行的过程中执行了一个读取文件的操作这个读取文件的操作要很长时间所以我们会交给另外一个函数去执行而当前的函数则继续向下执行假设很快就结束了生命周期结束那么这个时候就会将引用-1如果没有这个1那么引用计数变为0指针消失。等到读取文件的操作结束回调这个函数指针的时候发现这个函数指针已经被销毁直接抛出异常。而我们的1就保证了这个错误不会发生只有当整个程序完全的结束我们才会销毁这个指针。而在我们刚刚的一个例子里面就涉及到了另一个理解的点就是一个协程的函数里面套了另外一个函数这两个函数是没有任何关系的也就是说一个函数的结束不会影响另外一个函数。class Fiber : public std::enable_shared_from_thisFiber { public: void doSomethingAsync() { // 假设这里要发起一个异步操作需要把“我自己”传给回调函数 // 如果直接传 this 指针异步操作完成时这个 Fiber 对象可能已经被销毁了 // 正确做法在内部安全地获取自己的 shared_ptr延长生命周期 auto self shared_from_this(); asyncCall([self]() { // 只要 self 还在Fiber 对象就绝对不会被释放 self-onComplete(); }); } };Thread类的实现协程是运行在线程里面的所以线程线程就是协程运行的环境我们主要有两个类一个是信号量这个信号量就是为了保证创建线程的时候资源不会发生冲突一个是线程这个线程里面的主要函数一个是构造函数一个是run函数#ifndef THREAD_H #define THREAD_H #include condition_variable #include mutex #include functional namespace fengyue { class Semaphore { private: std::mutex mutex; std::condition_variable cv; int count; public: explicit Semaphore(int count_ 0) : count(count_) {} // p操作 void wait() { std::unique_lockstd::mutex lock(mutex); while (count 0) { cv.wait(lock); } count--; } // v操作 void signal() { std::unique_lockstd::mutex lock(mutex); count; cv.notify_one(); // 这里的one指的不止有一个线程可能有多个线程 } }; /* 创建并管理底层线程为协程提供运行环境同时通过线程局部存储和同步机制为协程调度提供必要条件 */ class Thread { public: Thread(std::functionvoid() func, const std::string name); ~Thread(); pid_t getId() const {return m_id;} // 该Thread管理的线程的id const std::string getName() const {return m_name;} void join(); // 等待线程执行完成 public: static pid_t GetThread(); // 获取系统分配线程的id当前执行上下文的进程 static Thread* GetThis(); // 获取当前所在的进程 static const std::string GetName(); // 获取当前线程的名称 static void SetName(const std::string name); // 设置当前线程的名称 private: static void* run(void* arg); private: pid_t m_id -1; pthread_t m_thread 0; std::string m_name; // 线程名称 std::functionvoid() m_func; // 线程运行函数 Semaphore m_semaphore; // 引入信号量的类来完成线程的同步创建操作 }; } #endif首先我们用thread_local将每一个线程作为副本隔离开防止互相的干扰然后设置了每一个线程对应的指针。构造函数我们需要在创建线程的时候传递这个线程要执行的函数和函数的参数这里我们有两个细节一个是我们run使用的是静态函数因为pthread_create只接受c风格的参数而c的类函数里面自己包含了隐式的this指针导致函数不符合。所以我们用static消除this指针变成c风格的函数然后同时将this指针作为参数传递进去用void* arg来接受接受之后再一顿赋值保证执行这个函数的时候环境正确然后拿到我们创建线程时候传参构造的函数通过swap这么一个高效的赋值方式来把函数传递给func如果m_func很大那么就要拷贝很多东西效率极低我们这里选择浅拷贝最后我们看一下信号量是怎么运用的首先在创建线程的函数里面调用wait但是因为初始值是0所以这个线程睡着了然后创建的线程当创建完毕之后会发出信号唤醒这个主线程这个主线程才可以继续往下执行。这完美的避免了同时创建很多个要执行的线程。#include Thread.h #include sys/syscall.h #include iostream #include unistd.h namespace fengyue { /* static表示变量的生命周期在生命周期结束才会被销毁 thread_local表示变量是线程局部的每个线程都会拥有一个独立的Thread指针和当前对象的名称多个线程互相不干扰 */ static thread_local Thread* t_thread nullptr; // 当前线程的Thread对象指针 static thread_local std::string t_thread_name UNKNOWN; // 当前线程的名称 pid_t Thread::GetThread() { return syscall(SYS_gettid); // 一个系统调用用于获取当前线程的唯一ID。SYS_gettid 是Linux特定的系统调用编号 } void* Thread::run(void* arg) { Thread* thread (Thread*)arg; t_thread thread; t_thread_name thread-m_name; thread-m_id GetThread(); /* pthread_self()返回当前线程的ID用于设置线程名称 给当前线程在操作系统层面起一个专属的名字 只截取15个字节是因为Linux严格限制了名称长度最多只能有 16 个字节 */ pthread_setname_np(pthread_self(), thread-m_name.substr(0, 15).c_str()); std::functionvoid() func; func.swap(thread-m_func); thread-m_semaphore.signal(); func(); return nullptr; } Thread::Thread(std::functionvoid() func, const std::string name) : m_func(func), m_name(name) { int ret pthread_create(m_thread, nullptr, Thread::run, this); if (ret) { std::perror(pthread_create error); } m_semaphore.wait(); // 等待线程创建完成 } }总结本篇文章到这里就结束了希望可以帮助大家理解协程库~~~

相关新闻

上班族10分钟轻养生挑战!7天亲测:气血感/睡眠/脾胃全记录

上班族10分钟轻养生挑战!7天亲测:气血感/睡眠/脾胃全记录

为什么是"10分钟轻养生"很多人听到"养生",第一反应是:没时间、太复杂、坚持不下来。但养生的本质,是在日常习惯里做微小但持续的改变。每天10分钟,做满7天,身体的反馈往往比想象中明显。这次挑战&…

2026/7/1 4:17:19阅读更多 →
2026 年芝加哥自动化展聚焦人形机器人:工业应用重任务解决能力而非外形!

2026 年芝加哥自动化展聚焦人形机器人:工业应用重任务解决能力而非外形!

真正重要的,不是像不像人,而是会不会解决问题人形机器人因外形熟悉易让人将“像人”与“聪明”联系,适合展示和吸引投资。但企业应用时更关注其认知能力,具备该能力的机器人不一定像人,人形设计有优势但特定任务下轮式…

2026/7/1 4:12:19阅读更多 →
从亚麻布到汽车音响:为什么喇叭音盆材料会影响声音?

从亚麻布到汽车音响:为什么喇叭音盆材料会影响声音?

很多人第一次了解汽车音响,会把注意力放在“功率多大”“低音强不强”“喇叭尺寸几寸”这些参数上。但真正影响声音质感的,往往还有一个更基础的部分:喇叭振膜,也就是我们常说的音盆材料。音盆负责把电信号转换成空气振动&#xf…

2026/7/1 4:12:19阅读更多 →
别再手动算排名了!用Matlab实现TOPSIS评价模型,5分钟搞定水质评估案例

别再手动算排名了!用Matlab实现TOPSIS评价模型,5分钟搞定水质评估案例

别再手动算排名了!用Matlab实现TOPSIS评价模型,5分钟搞定水质评估案例水质评估是环境监测中的常见需求,但面对pH值、溶解氧、氨氮含量等多项指标时,如何科学量化不同采样点的综合水质水平?传统方法往往依赖专家经验或简…

2026/7/1 5:17:22阅读更多 →
爬虫开发实战:识别与规避反爬蜜罐(Web陷阱)的技术指南

爬虫开发实战:识别与规避反爬蜜罐(Web陷阱)的技术指南

1. 项目概述:当爬虫遇上“甜蜜的陷阱”做爬虫开发的朋友,估计没少和“反爬虫”斗智斗勇。从简单的User-Agent校验、IP频率限制,到复杂的验证码、动态加密参数,这些明面上的对抗大家已经习以为常。但今天要聊的,是一种更…

2026/7/1 5:17:22阅读更多 →
西安军工科研院所首选:满足信创要求的国产数字孪生仿真引擎有哪些?

西安军工科研院所首选:满足信创要求的国产数字孪生仿真引擎有哪些?

一、 西安军工产业的“信创”与“数字孪生”双重挑战西安,作为我国重要的国防科技工业基地,聚集了大量的军工科研院所和装备制造企业。这些单位在进行数字化转型,尤其是构建数字孪生系统时,面临着两个极为特殊且必须解决的核心矛盾…

2026/7/1 5:17:22阅读更多 →
周报日报生成与办公效率工具应用指南

周报日报生成与办公效率工具应用指南

一、职场周报日报场景的效率痛点 职场中,周报日报是高频但耗时的工作环节。多数职场人写周报时,需从零散的工作记录、会议纪要、项目数据中汇总内容,回忆与整理占据大量时间;日报则因每日重复撰写,易陷入格式固化、内…

2026/7/1 5:17:22阅读更多 →
保姆级教程:用LLaMA Factory的Web UI,在单张V100上微调Yi-6B模型(附完整参数配置)

保姆级教程:用LLaMA Factory的Web UI,在单张V100上微调Yi-6B模型(附完整参数配置)

零门槛实战:在单卡V100上通过Web UI高效微调Yi-6B模型当大模型技术席卷全球时,许多开发者和研究者却被复杂的命令行操作和晦涩的参数配置挡在门外。本文将带你用最直观的Web界面,在一张V100显卡上完成Yi-6B模型的完整微调流程。无需担心显存不…

2026/7/1 5:17:22阅读更多 →
CAD二次开发中DoubleCollection用法详解

CAD二次开发中DoubleCollection用法详解

在 AutoCAD .NET API 中,DoubleCollection 是一个用于存储双精度浮点数集合的类,常用于定义多段线(Polyline)的顶点坐标或样条曲线的拟合点等。 核心用法 DoubleCollection 通常作为参数传递给需要一系列连续数值的构造函数或方…

2026/7/1 5:12:22阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/7/1 4:42:14阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/7/1 5:19:01阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/1 0:01:44阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/1 0:01:44阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/1 0:01:44阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/1 0:01:44阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/1 0:01:44阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/1 0:01:44阅读更多 →