yc调度有几种模式,Ntyco协程之间调度的实现
yc调度有几种模式,Ntyco协程之间调度的实现就绪(ready)集合并不没有设置优先级的选型, 所有在协程优先级一致, 所以可以使用队列来存储就绪的协程, 简称为就绪队列(ready_queue)。储?TAILQ_INSERT_TAIL(&co->sched->ready co ready_next);协程在运行完成后,进行 IO 操作,此时 IO 并未准备好,进入等待状态集合;RB_FIND(_nty_coroutine_rbtree_wait &sched->waiting &find_it);IO 准备就绪,协程开始运行,后续进行 sleep 操作,此时进入到睡眠状态集合。RB_MIN(_nty_coroutine_rbtree_sleep &sched->sleeping);就绪(ready), 睡眠(sleep), 等待(wait)集合该采用如何数据结构来存
我们来思考个问题。
协程的运行状态包含{就绪,睡眠,等待},运行体回调函数,回调参数,栈指针,栈大小,当前运行体。
调度器的运行状态包含{就绪,睡眠,等待},一个运行体如何高效地在多种状态集合更换。 调度器与运行体的功能界限。
协程的运行状态新创建的协程, 创建完成后, 加入到就绪集合, 等待调度器的调度;
TAILQ_INSERT_TAIL(&co->sched->ready co ready_next);
协程在运行完成后,进行 IO 操作,此时 IO 并未准备好,进入等待状态集合;
RB_FIND(_nty_coroutine_rbtree_wait &sched->waiting &find_it);
IO 准备就绪,协程开始运行,后续进行 sleep 操作,此时进入到睡眠状态集合。
RB_MIN(_nty_coroutine_rbtree_sleep &sched->sleeping);
就绪(ready), 睡眠(sleep), 等待(wait)集合该采用如何数据结构来存
储?
就绪(ready)集合并不没有设置优先级的选型, 所有在协程优先级一致, 所以可以使用队列来存储就绪的协程, 简称为就绪队列(ready_queue)。
#define TAILQ_LAST(head headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last))
睡眠(sleep)集合需要按照睡眠时长进行排序,采用红黑树来存储, 简称睡眠树(sleep_tree)红黑树在工程实用为<key value> key 为睡眠时长,value 为对应的协程结点。
等待(wait)集合,其功能是在等待 IO 准备就绪,等待 IO 也是有时长的,所以等待(wait)集合采用红黑树的来存储,简称等待树(wait_tree),此处借鉴 nginx 的设计。
#define RB_INSERT(name x y) name##_RB_INSERT(x y)#define RB_REMOVE(name x y) name##_RB_REMOVE(x y)#define RB_FIND(name x y) name##_RB_FIND(x y)#define RB_NFIND(name x y) name##_RB_NFIND(x y)#define RB_NEXT(name x y) name##_RB_NEXT(y)#define RB_PREV(name x y) name##_RB_PREV(y)#define RB_MIN(name x) name##_RB_MINMAX(x RB_NEGINF)#define RB_MAX(name x) name##_RB_MINMAX(x RB_INF)
数据结构如图所示:
Coroutine就是协程的相应属性,status表示协程的运行状态。sleep与wait两颗红黑树,ready使用的队列,比如某协程调用sleep函数,加入睡眠树(sleep_tree),status |= S即可。比如某协程在等待树(wait_tree)中,而IO准备就绪放入ready队列中,只需要移出等待树(wait_tree),状态更改status &= ~W即可。有一个前提条件就是不管何种运行状态的协程,都在就绪队列中,只是同时包含有其他的运行状态。
这是由ntyco作者亲自讲解的协程课程
1.协程起源 — 存在的原因?协程能够解决哪些问题?
- 协程起源 — 存在的原因?
- 如何使用?与线程使用有何区别?
- 内部是如何工作的?
- 原语操作有哪些?分别如何实现?
02协程实现之切换 — 上下文如何切换?代码如何实现?
- 运行体如何定义?调度器如何定义?
- 协程如何被调度?
- 协程多核模式 — 多核实现
- 协程性能测试 — 实战性能测试
协程学习地址:纯C语言|实现协程框架,底层原理与性能分析,面试利刃-学习视频教程-腾讯课堂
每一协程都需要使用的而且可能会不同属性的,就是协程属性。每一协程都需要的而且数据一致的,就是调度器的属性。比如栈大小的数值,每个协程都一样的后不做更改可以作为调度器的属性,如果每个协程大小不一致,则可以作为协程的属性。
用来管理所有协程的属性,作为调度器的属性。比如epoll用来管理每一个协程对应的IO,是需要作为调度器属性。
我们定义一个协程结构体需要多少域,我们描述了每一个协程有自己的上下文环境,需要保存CPU的寄存器ctx;需要有子过程的回调函数func;需要有子过程回调函数的参数 arg;需要定义自己的栈空间 stack;需要有自己栈空间的大小 stack_size;需要定义协程的创建时间 birth;需要定义协程当前的运行状态 status;需要定当前运行状态的结点(ready_next wait_node sleep_node);需要定义协程id;需要定义调度器的全局对象 sched。
协程的结构体:
typedef struct _nty_coroutine {
//private nty_cpu_ctx ctx; proc_coroutine func; void *arg; void *data; size_t stack_size; size_t last_stack_size; nty_coroutine_status status; nty_schedule *sched;
uint64_t birth; uint64_t id;#if CANCEL_FD_WAIT_UINT64 int fd; unsigned short events; //POLL_EVENT#else int64_t fd_wait;#endif char funcname[64]; struct _nty_coroutine *co_join;
void **co_exit_ptr; void *stack; void *ebp; uint32_t ops; uint64_t sleep_usecs;
RB_ENTRY(_nty_coroutine) sleep_node; RB_ENTRY(_nty_coroutine) wait_node;
LIST_ENTRY(_nty_coroutine) busy_next;
TAILQ_ENTRY(_nty_coroutine) ready_next; TAILQ_ENTRY(_nty_coroutine) defer_next; TAILQ_ENTRY(_nty_coroutine) cond_next;
TAILQ_ENTRY(_nty_coroutine) io_next; TAILQ_ENTRY(_nty_coroutine) compute_next;
struct { void *buf; size_t nbytes; int fd; int ret; int err; } io;
struct _nty_coroutine_compute_sched *compute_sched; int ready_fds; struct pollfd *pfds; nfds_t nfds;} nty_coroutine;
调度器是管理所有协程运行的组件,协程与调度器的运行关系。
调度器的属性,需要有保存CPU的寄存器上下文 ctx,可以从协程运行状态yield到调度器运行的。从协程到调度器用yield,从调度器到协程用resume。
typedef struct _nty_coroutine_rbtree_sleep nty_coroutine_rbtree_sleep;typedef struct _nty_coroutine_rbtree_wait nty_coroutine_rbtree_wait;
typedef struct _nty_schedule { uint64_t birth; nty_cpu_ctx ctx; void *stack; size_t stack_size; int spawned_coroutines; uint64_t default_timeout; struct _nty_coroutine *curr_thread; int page_size;
int poller_fd; int eventfd; struct epoll_event eventlist[NTY_CO_MAX_EVENTS]; int nevents;
int num_new_events; pthread_mutex_t defer_mutex;
nty_coroutine_queue ready; nty_coroutine_queue defer;
nty_coroutine_link busy; nty_coroutine_rbtree_sleep sleeping; nty_coroutine_rbtree_wait waiting;
//private
} nty_schedule;
协程如何被调度1.生产者消费者模式
逻辑代码如下:
while (1) {
//遍历睡眠集合,将满足条件的加入到ready nty_coroutine *expired = NULL; while ((expired = sleep_tree_expired(sched)) != ) { TAILQ_ADD(&sched->ready expired); }
//遍历等待集合,将满足添加的加入到ready nty_coroutine *wait = NULL; int nready = epoll_wait(sched->epfd events EVENT_MAX 1); for (i = 0;i < nready;i ) { wait = wait_tree_search(events[i].data.fd); TAILQ_ADD(&sched->ready wait); }
// 使用resume回复ready的协程运行权 while (!TAILQ_EMPTY(&sched->ready)) { nty_coroutine *ready = TAILQ_POP(sched->ready); resume(ready); } }
2. 多状态下运行
逻辑代码如下:
while (1) {
//遍历睡眠集合,使用resume恢复expired的协程运行权 nty_coroutine *expired = NULL; while ((expired = sleep_tree_expired(sched)) != ) { resume(expired); }
//遍历等待集合,使用resume恢复wait的协程运行权 nty_coroutine *wait = NULL; int nready = epoll_wait(sched->epfd events EVENT_MAX 1); for (i = 0;i < nready;i ) { wait = wait_tree_search(events[i].data.fd); resume(wait); }
// 使用resume恢复ready的协程运行权 while (!TAILQ_EMPTY(sched->ready)) { nty_coroutine *ready = TAILQ_POP(sched->ready); resume(ready); } }