
本文共 2950 字,大约阅读时间需要 9 分钟。
1、协程是什么
在了解协程之前,需要对比一下进程、线程和协程的区别:
1.1 进程
进程可以理解为资源分配的基本单位,操作系统以一个进程为基本单位,分配系统资源,包括内存资源和CPU的时间片资源。进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。一个进程有自己独立的地址空间,包括:
- 数据段
- 代码段
- 堆
- 栈
- 文件
一个进程里面可以包含多个线程。
1.2 线程
线程是CPU调度的基本单位,线程之间的切换需要从用户态切换到内核态,由CPU进行调度。线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”, 切换效率中等。一个进程内的所有线程共享进程的资源,但是线程也有自己独立的资源,这些资源有:
- 线程ID:每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。
- 寄存器组的值:由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
- 线程的堆栈:堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。
- 错误返回码:由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。所以不同线程应该有自己的错误返回码变量。
- 线程的信号屏蔽码:由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。
- 线程的优先级:由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。
1.3 协程
协程,可以理解为用户态的线程,即一个线程可以包含多个协程,这些协程由用户创建,并且由用户控制进行协程的调度。协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是硬件上下文,切换内存保存在用户自己的变量(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。线程里面的协程对CPU而言是不能感知的,CPU只会感知到一个线程在运行。协程所拥有的资源主要有:
- 协程的上下文环境,即各种寄存器的值
- 协程拥有的堆栈
2、libco库跑hello world
libco是微信后台大规模使用的c/c++协程库,2013年至今稳定运行在微信后台的数万台机器上。libco通过仅有的几个函数接口 co_create/co_resume/co_yield 再配合 co_poll,可以支持同步或者异步的写法,如线程库一样轻松。同时库里面提供了socket族函数的hook,使得后台逻辑服务几乎不用修改逻辑代码就可以完成异步化改造。
Github地址:.
#include#include #include #include #include "co_routine.h"using namespace std;struct stTask_t{ int id;};struct stEnv_t{ stCoCond_t* cond; queue task_queue;};void* Producer(void* args){ co_enable_hook_sys(); stEnv_t* env= (stEnv_t*)args; int id = 0; while (true) { stTask_t* task = (stTask_t*)calloc(1, sizeof(stTask_t)); task->id = id++; env->task_queue.push(task); printf("%s:%d produce task %d\n", __func__, __LINE__, task->id); co_cond_signal(env->cond); poll(NULL, 0, 1000); } return NULL;}void* Consumer(void* args){ co_enable_hook_sys(); stEnv_t* env = (stEnv_t*)args; while (true) { if (env->task_queue.empty()) { co_cond_timedwait(env->cond, -1); continue; } stTask_t* task = env->task_queue.front(); env->task_queue.pop(); printf("%s:%d consume task %d\n", __func__, __LINE__, task->id); free(task); } return NULL;}int main(){ stEnv_t* env = new stEnv_t; env->cond = co_cond_alloc(); stCoRoutine_t* consumer_routine; co_create(&consumer_routine, NULL, Consumer, env); co_resume(consumer_routine); stCoRoutine_t* producer_routine; co_create(&producer_routine, NULL, Producer, env); co_resume(producer_routine); co_eventloop(co_get_epoll_ct(), NULL, NULL); return 0;}
这是一个生产者-消费者示例。在这个示例中,主要有两个函数:
- 生产者函数:Producer负责将任务放入任务队列之中
- 消费者函数:Consumer负责从任务队列中取任务来执行
main函数中,分别为这两个函数创建两个协程进行来执行,这里主要用到了libco的两个接口,分别是co_create()和co_resume(),co_create()负责创建一个协程,而co_resume()负责启动一个协程。这里协程的创建和线程的创建有很大不同,线程创建时可以直接运行,而协程创建后并没有运行,而是需要调另外一个函数才能运行。
发表评论
最新留言
关于作者
