协程可以说是 golang 中的有名的框架,本文主要分析 Github 项目 Ntyco 协程框架的实现,由于本人目前 golang 写的不多,因此不会对 golang 的源码进行分析,只是根据 golang 的协程调度来分析 c 语言版本调度。
golang 中大名鼎鼎的协程有这样三个元素 G M P,G 表示 goroutinue 协程,m 是内核元素, p 表示处理器,用来管理和执行协程。
如果使用 c 语言的话,那么内核线程使用 epoll 进行管理最好,而在 golang ,当然我们主要实现的就是 goroutine 和 process ,说白了,我们需要设计一个协程的数据结构并对其进行操作,然后实现一个协程调度其对齐进行调度,并且和内核之间进行通信。
项目中的代码结构如下:
所有的实现代码都在 core 目录下。
其中最关键的代码在 nty_coroutine 、 nty_schedule 和 nty_socket 中,其中 nty_tree ,是对红黑树的各种操作实现,nty_queue 是对队列的操作实现。
我们先从协程开始,关于协程的数据结构定义如下:
说几个关键的内容一个是协程的状态,golang 中协程状态有三种,一种是 sleep 沉睡,另一种是 ready 在准备,然后正在运行,这里用了红黑树维护各个节点,没然后用队列维护正在准备的节点。
定义的状态如下:
上述状态就是各种细分的协程状态。
协程定义之后,就要对数据结构进行操作,然后让协程跟我们的调度进行进行交互。
这里只展示了对于协程的调度以及对于协程的初始化,另外还有协程创建,状态转换,加入不同状态的队列中,这一部分的内容相对较为容易。
协程定义出来之后,我们需要这样的前置只是,协程到底要怎么调度,这就是我们需要对栈进行操作,在 x86 处理器上,我们汇编代码都是在栈上进行处理的,如果了解过 liunx 操作系统,我们就知道操作系统在从用户态切换到内核态就需要进行系统调用,这个时候会保留进程的上下文,然后从内核态执行完毕后就恢复过来。
因此我们在协程调度的数据结构中定义如下。
我们来看一下这个协程调度器的大概作用,首先就是跟协程进行交互,对协程的上下文进行保存加载。
另外就是跟内核线程进行交互,他需要将协程的内容加入内核 epoll 中进行调度。
先从调度器的创建开始
这里调度器就是对各种状态协程调度,同时针对内核线程的 epoll 也要进行交互。
注意,因为我们协程不可能只创建一个, golang 中,有多个 Process 进行处理,因此调度上也会需要对各调度的数据结构。
接着实现就是一些协程数据结构的对协程在各个节点之间转换操作,因为篇幅原因就不再赘述,我们最后来看,调度器跑起来的函数
上述就是一个针对睡眠,准备和等待队列的一个调度,睡眠的队列有个时间,当这个协程创建时间超过了这个时间,那么就要进入调度执行,对于准备的队列直接从队列中取然后执行,最后就是执行等待的队列。
在调度 run 函数中,我们开到最后是对 epoll 的调度,集中在 nty_schedule_epoll,nty_schedule_search_wait ,nty_coroutine_resume。
我们先来看看 nty_schedule_epoll 函数上:
函数较为见到那,通过 nty_epoller_wait 得到 nready 的时间,然后将时间的数量设置为 nready ,然后调度器后边就是会处理时间。
另外就是 nty_schedule_search_wait 就是从 wait 的红黑树中找到就绪时间,最后就是协程的恢复执行的一个过程。
上述就是一个简单的协程框架的分析,源码整个部分较为复杂,很多细节没有讲到,建议大家结合 golang 的原理跟着查看,关键内容在这个调度器的实现上,源码中还有 poll socket 之类网络 I/O的封装,不过不再本文讲述的范围内了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。