C/C++协程学习笔记丨C/C++实现协程及原理分析视频( 四 )
- 创建 socket , 监听在本机的 1024 端口 , 并设置为非阻塞;
- 主线程使用函数 readwrite_coroutine 创建多个读写协程 , 调用 co_resume 启动协程运行直到其挂起 。 这里我们忽略掉无关的多进程 fork 的过程;
- 主线程继续创建 socket 接收协程 accpet_co , 同样调用 co_resume 启动协程直到其挂起;
- 主线程调用函数 co_eventloop 实现事件的监听和协程的循环切换;
主线程中的函数 co_eventloop 监听网络事件 , 将来自于客户端新进的连接交由协程 accept_co 处理 , 关于 co_eventloop 如何唤醒 accept_co 的细节我们将在后续介绍 。 accept_co 调用函数 accept_routine 接收新连接 , 该函数的流程如下:
- 检查队列 g_readwrite 是否有空闲的读写 coroutine , 如果没有 , 调用函数 poll 将该协程加入到 Epoll 管理的定时器队列中 , 也就是 sleep(1000) 的作用;
- 调用 co_accept 来接收新连接 , 如果接收连接失败 , 那么调用 co_poll 将服务端的 listen_fd 加入到 Epoll 中来触发下一次连接事件;
- 对于成功的连接 , 从 g_readwrite 中取出一个读写协程来负责处理读写;
上面的过程大致说明了控制流程是如何在不同的协程中切换 , 接下来我们介绍具体的实现细节 , 即如何通过 Epoll 来管理协程 , 以及如何对系统函数进行改造以满足 libco 的调用 。
通过 Epoll 管理和唤醒协程Epoll 监听 FD
上一章节中介绍了协程可以通过函数 co_poll 来将 fd 交由 Epoll 管理 , 待 Epoll 的相应的事件触发时 , 再切换回来执行 read 或者 write 操作 , 从而实现由 Epoll 管理协程的功能 。 co_poll 函数原型如下:
int co_poll(stCoEpoll_t *ctx, struct pollfd fds[],
nfds_t nfds, int timeout_ms)
stCoEpoll_t 是为 libco 定制的 Epoll 相关数据结构 , fds 是 pollfd 结构的文件句柄 , nfds 为 fds 数组的长度 , 最后一个参数表示定时器时间 , 也就是在 timeout 毫秒之后触发处理这些文件句柄 。 这里可以看到 , co_poll 能够同时将多个文件句柄同时加入到 Epoll 管理中 。 我们先看 stCoEpoll_t 结构:
struct stCoEpoll_t
{
int iEpollFd; // Epoll 主 FD
static const int _EPOLL_SIZE = 1024 * 10; // Epoll 可以监听的句柄总数
struct stTimeout_t *pTimeout; // 时间轮定时器
struct stTimeoutItemLink_t *pstTimeoutList; // 已经超时的时间
struct stTimeoutItemLink_t *pstActiveList; // 活跃的事件
co_epoll_res *result; // Epoll 返回的事件结果
};
以 stTimeout_ 开头的数据结构与 libco 的定时器管理有关 , 我们在后面介绍 。 co_epoll_res 是对 Epoll 事件数据结构的封装 , 也就是每次触发 Epoll 事件时的返回结果 , 在 Unix 和 MaxOS 下 , libco 将使用 Kqueue 替代 Epoll , 因此这里也保留了 kevent 数据结构 。
struct co_epoll_res
{
int size;
struct epoll_event *events; // for linux epoll
struct kevent *eventlist; // for Unix or MacOs kqueue
};
co_poll 实际是对函数 co_poll_inner 的封装 。 我们将 co_epoll_inner 函数的结构分为上下两半段 。 在上半段中 , 调用 co_poll 的协程 CC 将其需要监听的句柄数组 fds 都加入到 Epoll 管理中 , 并通过函数 co_yield_env 让出 CPU;当 main 协程的事件循环 co_eventloop 中触发了 CC 对应的监听事件时 , 会恢复 CC的执行 。 此时 , CC 将开始执行下半段 , 即将上半段添加的句柄 fds 从 epoll 中移除 , 清理残留的数据结构 , 下面的流程图简要说明了控制流的转移过程:
文章插图
有了上面的基本概念 , 我们来看具体的实现细节 。 co_poll 首先在内部将传入的文件句柄数组 fds 转化为数据结构 stPoll_t , 这一步主要是为了方便后续处理 。 该结构记录了 iEpollFd , ndfs , fds 数组 , 以及该协程需要执行的函数和参数 。 有两点需要说明的是:
- 对于每一个 fd , 为其申请一个 stPollItem_t 来管理对应 Epoll 事件以及记录回调参数 。 libco 在此做了一个小的优化 , 对于长度小于 2 的 fds 数组 , 直接在栈上定义相应的 stPollItem_t 数组 , 否则从堆中申请内存 。 这也是一种比较常见的优化 , 毕竟从堆中申请内存比较耗时;
- 用于|用于半监督学习的图随机神经网络
- 今日|“舜网”学习强国号今日上线 济南报业全媒体矩阵再添新成员
- SK|SK电讯推出自研AI芯片SAPEON X220 深度学习计算速度是常用GPU 1.5倍
- 效果|这个让你相见恨晚的技巧,能让PPT排版更加有设计感,推荐学习
- 菜鸟学编程,不懂C++ this指针?还不赶快来学一学
- 学习C语言的软件,就突然被我绿了?
- Linux 之父对 C++ 进行了炮轰,C++不值得推荐?
- 学习python第二弹
- 喵喵机错题打印机P1:随时打印,随时学习,快速整理错题
- 爱可可AI论文推介(10月17日)