协程
一 协程出现的目的
同步IO: 先处理IO请求,在处理IO操作。然后回到IO请求,一直这么循环下去。
异步IO:处理玩IO请求后,不等IO操作的完成,就继续执行接下来的代码。IO操作的结果由其他方式告知发起IO请求的线程。
协程IO:在用户态实现异步。用同步的方式,编写出异步的性能。
同步IO的特点是便于理解,但是性能不高。异步IO则是性能较高,但是多个线程公用一个fd,比较麻烦。协程则集中了它们的优点,能用同步的方式,编写从异步的性能。
二 原语
原语是指实现某个特殊功能的且不能被打断的由若干条指令组成的程序段。
协程需要两个原语,分别是:yeild和resume。
1.yeild:跳转到另一个代码处。此原语分别执行保存目前代码的上下文和加载要跳转到的代码的上下文。
2.resume:恢复需要运行的协程。
这两个函数的底层是switch();
switch的实现原理:1.setjmp/longjmp
2.ucontext
3.汇编的代码
三 切换
遇到IO操作,就让出,回到由调度器决定的协程。
让出与选择协程是由原语操作的。
协程运行的流程:先是在调度器运行,然后通过resume切换到协程A,当yield时,回到调度器运行,再通过resume切换到协程B,再通过yeild回到调度器,之后周而复始。
四 协程结构体定义
#define queue_node(name, type) struct name {
struct type *next;
struct type *prev;
}
#define rbtree_node() {
char color;
struct type *right;
struct type *left;
struct type *parent;
}
struct cpu_register_set {
void *r1;
void *ebx;
...................
};
struct coroutine {
struct cpu_register_set *set; //cpu
void *func; //coroutine_entry
void *arg;
void *retval;//返回值
void *stack_addr;
size_t stack_size;
queue_node(ready_queue, coroutine) *ready;
rbtree_node(coroutine) *wait;
rbtree_node(coroutine) *sleep; //optional
};
coroutine_create(entry_cb, arg);
coroutine_join(coid, &ret) ;
四个状态:新建,就绪,睡眠,等待
新建,就绪:队列
等待:rbtree
睡眠:rbtree
五 调度策略
1.sleep
2.ready
3.wait ??问题?? cfs 1:26
先判断sleep和wait队列(先后顺序不一定), 将就绪好的协程加入就绪队列,然后运行就绪队列的协程。
六 调度器的定义
struct scheduler {
struct scheduler_ops *ops;
struct coroutine *cur;
int epfd;
queue_node *ready_set;
rbtree() *wait_set;
rbtree() *sleep_set;
};
struct scheduler_ops {
struct scheduler_ops *next;
enset();
deset();
}
七 协程api的实现
api主要针对的io操作:socket,bind,listen,accept,send.recv.close,connect.
封装api的方法:
Wang_accept(){
int ret = poll(fd),//后边时间参数设置为0
if (ret > 0) {
accept();
} else {
epoll_ctl(epfd, );
yield();
}
通过init_hook函数,重新定义系统调用名,使自己写的api的名字和原系统调用保持一直。
八 多核模式
使用CPU多核模式的三种方法:
1.多线程,cpu亲缘性
2.多进程(建议这个)
3.cpu指令
九 测试结果
对于单个IO请求和操作,协程不一定比epoll快,但是当由很多IO请求和操作的时候,协程速度优势就比较明显了。协程在局部不一定快,但是在整体上比较快。
红黑树与小根堆?