1.什么是协程
正如网络上大家都在讲的“用户态的线程,更轻量级”,
2.协程跟进程,线程区别
2.1进程
1.程序的一次执行过程;
2.系统资源分配的单位,具有独立的虚拟内存,PCB控制块,代码,数据,堆栈,文件资源等;
3.进程上下文切换开销大(主要问题)
2.2线程
1.系统调度的单位;
2.共享进程的资源空间;具有自己的栈,寄存器,
3.并发性好,同一进程内的线程切换不会导致进程的上下文切换,所以线程切换开销小;
4.共享进程资源,多线程会发生冲突,需要进行同步。
2.3协程
1.可以理解为线程中的线程;
2.具有自己的上下文;
3.协程切换不会引起线程的上下文(更轻量)
4.一个线程同一时刻只有一个协程执行;
5.协程执行任意时刻都可以退出,下次可以继续执行(与函数的区别,函数一条路走到黑)
6.高并发场景下性能很好,并且不需要同步
3.为什么要产生协程
其实协程效率高并不是根本原因;
在高并发场景下
两个原因:
1.用同步代码逻辑简单,并发性差强人意
同步阻塞:阻塞等待业务处理,期间不能做其他事情
同步非阻塞:不断轮询,忙等待占用cpu,
2.用异步,并发性好,代码逻辑又很复杂
基于以上就产生了协程,协程可以做到用同步的代码逻辑达到异步的效果
4.协程的优缺点
优点:
1.用同步的思想编写出异步的效果,
2.并发性好,性能高
缺点:
单线程中同时只能执行一个协程,无法利用多核功能,还是得结合多线程
5.常见的协程库
Boost.Coroutine2:这是Boost库中的⼀个模块,提供了⼀组灵活的协程实现,包括对称协程和⾮对称协程。它提供了⼀些强⼤的特性,如⽀持多个栈和⾃定义栈⼤⼩。
libco:腾讯微信团队开源的⼀个C/C++协程库,据说微信后台⼤量在使⽤这个库,通过⼏个简单的接⼝就能实现协程的创建/调度,同时基于epoll/kqueue实现了⼀个事件反应堆,再加上sys_call(系统调⽤)hook技术,以此给开发者提供同步/异步的开发⽅法
libgo :是⼀个使⽤ C++ 编写的协作式调度的stackful有栈协程库, 同时也是⼀个强⼤的并⾏编程库。⽀持linux平台,MacOS和windows平台,在c++11以上的环境中都能⽤。
6.ucontext库的使用
6.1协程上下文ucontext结构体
typedef struct ucontext_t {
struct ucontext_t *uc_link; //该上下文指向的下一个上下文
stack_t uc_stack; //栈的信息,是一个stack_t结构体
mcontext_t uc_mcontext; //保存了上下文各种寄存器信息
sigset_t uc_sigmask; //当上下文被激活时屏蔽的信号集
} ucontext_t;
6.2栈信息结构体
typedef struct {
void *ss_sp;//栈空间指针,指向栈空间的地址
int ss_flags;//栈的flags
size_t ss_size;//栈的大小
} stack_t;
6.3ucontext还提供了四个API函数
#include<ucontext.h>
//获取协程上下文
int getcontext(ucontext_t *ucp);
//设置协程上下文
int setcontext(const ucontext_t *ucp);
/*设置协程入口函数,使用前需要配合getcontext()使用,
如果指定了ucontext中的uc_link,那么函数执行完会返回到uc_link执向的上下文*/
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
//保存当前上下文到oucp,前往ucp执向的上下文,相当于切换上下文
int swapcontext(ucontext_t *oucp, ucontext_t *ucp);
说这么多不如上案例,
1.先来试试getcontext和setcontext的用法
#include <ucontext.h>
#include <iostream>
void func(int a)
{
std::cout << "func begin" << std::endl;
std::cout << a++ << std::endl;
std::cout << "func end" << std::endl;
}
int main()
{
ucontext m_ctx, c_ctx;//定义两个上下文
getcontext(&m_ctx);//初始化m_ctx,此时记录执行位置为下一行
func(10);//调用func
setcontext(&m_ctx);/*设置上下文m_ctx,m_ctx的执行位置是调用func的那一行,又回到刚才的位置
所以程序无线循环*/
return 0;
}
可以看到程序无线循环打印a的值,不会退出
2. 我们再试试makecontext的用法
#include <ucontext.h>
#include <iostream>
#include <stdlib.h>
int a = 10;
void func()
{
std::cout << "func begin" << std::endl;
std::cout << a++ << std::endl;
std::cout << "func end" << std::endl;
}
int main()
{
ucontext m_ctx, c_ctx;
getcontext(&m_ctx); // 初始化上下文
m_ctx.uc_link = nullptr; // uc_link指向执行完入口函数返回的位置,为nullptr则退出,
m_ctx.uc_stack.ss_sp = malloc(128 * 1024); //
m_ctx.uc_stack.ss_size = 128 * 1024;
makecontext(&m_ctx, func, 0); // 将上下文绑定入口函数
setcontext(&m_ctx); // 执行该上下文
return 0;
}
可以看到我们执行完入口函数就结束了
3.再来试试swapcontext
#include <ucontext.h>
#include <iostream>
#include <stdlib.h>
int a = 10;
ucontext m_ctx, c_ctx;
void func()
{
std::cout << "func begin" << std::endl;
swapcontext(&m_ctx, &c_ctx); // 保存当前上下文,切换到ctx上下文
std::cout << a++ << std::endl;
std::cout << "func end" << std::endl;
}
int main()
{
getcontext(&m_ctx); // 初始化上下文
m_ctx.uc_link = nullptr; // uc_link指向执行完入口函数返回的位置,为nullptr则退出,
m_ctx.uc_stack.ss_sp = malloc(128 * 1024); //
m_ctx.uc_stack.ss_size = 128 * 1024;
makecontext(&m_ctx, func, 0); // 将上下文绑定入口函数
swapcontext(&c_ctx, &m_ctx); // 将当前上下文保存到c_ctx, 执行m_ctx上下文
return 0;
}
可以看到函数执行中途就切换回来了,那么能不能再切换回去呢,答案是肯定的
4.我们在主函数再加一个swap再切换回去
#include <ucontext.h>
#include <iostream>
#include <stdlib.h>
int a = 10;
ucontext m_ctx, c_ctx;
void func()
{
std::cout << "func begin" << std::endl;
swapcontext(&m_ctx, &c_ctx); // 保存当前上下文,切换到ctx上下文
std::cout << a++ << std::endl;
std::cout << "func end" << std::endl;
}
int main()
{
getcontext(&m_ctx); // 初始化上下文
m_ctx.uc_link = nullptr; // uc_link指向执行完入口函数返回的位置,为nullptr则退出,
m_ctx.uc_stack.ss_sp = malloc(128 * 1024); //
m_ctx.uc_stack.ss_size = 128 * 1024;
makecontext(&m_ctx, func, 0); // 将上下文绑定入口函数
swapcontext(&c_ctx, &m_ctx); // 将当前上下文保存到c_ctx, 执行m_ctx上下文
swapcontext(&c_ctx, &m_ctx);
return 0;
}
可以看到又切换会回函数内部继续执行了,代码这里没有加区分的语句,加了的话就更明显了
5.由此我们是不是可以联想到协程可以做很多事情,比如执行某个系统调用阻塞了,我们可以暂时切换到别的协程去执行别的操作,等一段时间再来执行原来的协程。这样效率是不是更高了。
6.协程相关概念
6.1非对称协程跟对称协程
非对称:每个子协程都由主协程调度执行,退出也要返回到主协程,主协程再调度其他的子协程
主协程:每个协程在自己内部可以调度其他协程,不需要主协程充当媒介来调度。
6.2有栈协程跟无栈协程
有栈协程:有自己的栈寄存器,可以绑定入口函数执行
无栈协程:一般只用来调度,如主协程
6.3共享栈协程
1.多个协程共享一个栈,可以节约空间
2.一般我们都是独享栈,比较浪费空间资源,但是也便于操作