什么是协程(一次性讲清)

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.先来试试getcontextsetcontext的用法

#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.一般我们都是独享栈,比较浪费空间资源,但是也便于操作

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值