C++跨平台协程实现

概念

1、进程

  • 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
  • 进程和进程之间占有独立的内存空间,不共享数据,不同进程之间可以通过FIFO,PIPE等共享数据。

2、线程

  • 线程是进程的一个执行单元,是CPU调度和分配的基本单位。
  • 线程不具有独立的运行资源,进程内的不同线程共享进程分配到的内存空间。
  • 线程和线程之间通过内存共享共享数据,可以较为快速的进行context切换。资源开销较小。

3、协程

  • 协程是用户态轻量级线程。虽然叫用户态线程,但是协程的create并不需要调用系统资源,而是由语言内部进行实现的。所以协程可以大量创建。
  • 协程是跑在线程上的线程片,并且可以人为的选择创建,销毁,切换,挂起,恢复。
  • 协程和线程之间最大的不同在于,对于多核CPU,线程可以并行执行程序,而协程是跑在线程上的线程片,只能并发。
  • 协程在并发上的修饰在于,创建,切换所消耗的资源远远小于线程创建和切换的开销。所以,对于IO密集型场景,协程就极为匹配。

三者之间的关系,可以用图片表示
在这里插入图片描述

还有就是有很多小伙伴会问,协程既然这么好用,其使用场景是什么。网上有很多讲协程的文章,但是的确没有怎么说,协程的使用场景。现在很多互联网大厂都在致力于做自己的编译器和脚本运行工具,当脚本语言在运行到异步逻辑的时候,如果都通过线程和加锁来实现,无疑,这样,脚本的运行效率会相当低下。这时,如果用协程来实现脚本运行阶段的异步逻辑,会大大加速脚本的运行速度。

C++实现

c++ 实现协程分系统不同,有不同实现。

1、windows系统

windows系统中,微软针对coroutine提供了自己的一套API,起了一个名字叫Fiber(纤程),这组API智能在windows平台进行使用,无法跨平台。Fiber的两个特点

  • 纤程拥有独立的栈空间和寄存器环境;
  • 纤程在用户态实现调调度,也就是说完全由程序员控制;

接下来,来了解一下windows系统下如何使用这套API

  • 线程转换为纤程
LPVOID ConvertThreadToFiberEx(
  [in, optional] LPVOID lpParameter,//指针类型数据传入,可以通过GetFiberData索回数据
  [in]           DWORD  dwFlags /*可以填0和 FIBER_FLAG_FLOAT_SWITCH 两种状态。
  0的话,因为x86系统的CPU的浮点数状态信息默认情况下在纤程看来不属于CPU寄存器,浮点运算会导致错误。
  FIBER_FLAG_FLOAT_SWITCH可以解决这个问题*/
);

此接口是为了让开发者能够使用Fiber调度所进行操作。要想使用Fiber,就要把当前线程变成Fiber。需要注意的是此接口只在windowsXP之上的系统可以使用,而windowsXP系统,使用如下接口。

LPVOID ConvertThreadToFiber(
  [in, optional] LPVOID lpParameter
);

ConvertThreadToFiber(Ex)返回Fiber的运行地址,之后就可以使用这个地址。

  • 创建纤程

我们将线程转换成Fiber是为了有多个Fiber能够并发,这就需要我们进行Fiber的创建。

LPVOID CreateFiberEx(
  [in]           SIZE_T                dwStackCommitSize, //堆栈的初始化大小,默认为1MB(默认填0)
  [in]           SIZE_T                dwStackReserveSize,//预留的虚拟内存,默认为1MB(默认填0)
  [in]           DWORD                 dwFlags,
  [in]           LPFIBER_START_ROUTINE lpStartAddress, // Fiber运行函数指针
  [in, optional] LPVOID                lpParameter
);

同样,windowsXP版本之上才能够使用,XP版本使用

LPVOID CreateFiber(
  [in]           SIZE_T                dwStackSize,
  [in]           LPFIBER_START_ROUTINE lpStartAddress,
  [in, optional] LPVOID                lpParameter
);

像ConvertThreadToFiber(Ex)函数一样,CreateFiber(Ex)也返回纤程执行环境的内存地址,这个内存地址就像句柄一样,直接标识着一个纤程。

  • 切换纤程

创建纤程后,系统不会像开辟Thread时,自动运行,而是需要程序中由程序员决定何时运行。这就需要用到纤程的切换

void SwitchToFiber(
  [in] LPVOID lpFiber
);

lpFiber就是ConvertThreadToFiber(Ex)和CreateFiber(Ex)的返回值。

  • 删除纤程

当一个纤程运行完毕之后,需要删除,调用删除纤程的接口

void DeleteFiber(
  [in] LPVOID lpFiber
);

2、Linux系统

前文提到,Fiber只能在windows系统中使用,在linux上,需要用到ucontext。

the ucontext_t type is a structure type suitable for holding the context for the user thread of execution. A thread’s context include stack,saved registersm a list of block signals

这需要引入头文件
<ucontext.h>
其中包含了ucontext_t和配套的四个函数

#include <ucontext.h>
typedef struct ucontext_t {
   
  struct ucontext_t* uc_link;
  stack_t uc_stack;
  __sigset_t uc_sigmask;
  mcontext_t uc_mcontext;
  ...
};
  • uc_link 后续执行的context,若uc_link为空,当前context执行完毕后,退出程序
  • uc_stack 当前context的栈
  • uc_sigmask 当前context需要屏蔽的信号集合
  • uc_mcontext 保存上下文的特定机器, 包括调用线程的特定寄存器等等
#include <ucontext.h>
int getcontext(ucontext_t* ucp);
void makecontext(ucontext_t* ucp, void (*func)(), int argc, ...);
int swapcontext(ucontext_t* olducp, ucontext_t* newucp);
int setcontext(const ucontext_t* ucp);
  • getcontext

创建一个ucontext_t对象,进行执行空间分配
创建方式:
(1) getcontext,
(2) 指定分配给上下文的栈uc_stack.ss_sp,
(3) 指定这块栈的大小uc_stack.ss_size,
(4) 指定uc_stack.ss_flags,
(5) 指定后继上下文uc_link

ucontext_t ucp;
getcontext(&ucp);
ucp.uc_stack.ss_sp = new char[100];
ucp.uc_stack.ss_size = new char[100];
ucp.uc_stack.ss_flags = 0;

协程运行时使用主协程划分的栈空间,而协程切回主线程时需要将该部分栈空间的内容copy到每个协程各自的一个空间缓存起来,因为主协程中划分的栈空间并不是只用于一个协程,而是会用于多个协程

  • makecontext

makecontext可以修改getcontext初始化过的context,func参数指明了该context的入口函数,argc是入参个数

  • swapcontext

切换context,保存当前context并切换到新的context运行

  • setcontext

将当前程序切换到新的context,在执行正确的情况下该函数直接切换到新的执行状态,不会返回。

代码实现

思路

  • 协程调度类,可以当做协程池,用来管理用户协程对象
  • 用户协程类,用户协程类负责实际的协程接口实现,以及任务相关的部分
  • 任务类,用来执行具体业务

代码结构

在这里插入图片描述

  • corountine.h 和 coroutine.cpp 是用户携程类
  • coroutine_pool.h 和 coroutine_pool.h 是协程调度类,也就是协程池
  • coroutine_task.h 和 count.h 分别是任务类基类以及具体实现数数的任务类
  • coroutine_common.h 和 type.h 是自定义资源

代码具体实现在附录

附录

  • coroutine.h
#pragma once
#ifndef __Coroutine_H__
#define __Coroutine_H__
#include "type.h"
#include "coroutine_common.h"
#include "coroutine_task.h"
class CoroutinePool;
class Coroutine
{
   
public:
	Coroutine(CoroutineId id, std::shared_ptr<CoroutinePool> coroutinePool);
	virtual ~Coroutine();

public:
	error_t initialize();

	void unInitialize();

	error_t setStatus(const CoroutineStatusEnum& status);

	CoroutineStatusEnum getStatus() const;

	void setResumeReason(const ResumeReason& reason);

	ResumeReason getResumeReason() const;

	CoroutineId getCoroutineId() const;

	error_t setCoroutineTask(const CoroutineTaskPtr& task);

	CoroutineContext getContext() const;

	static void coroutineEntry(void* lpParameter);

	void resetContext();

private:

	error_t getCoroutinePool(OUT std::shared_ptr<CoroutinePool>& coroutinePool);

	CoroutineContext getMainContext() const;

	void run();

private:
	std::weak_ptr<CoroutinePool>      m_coroutinePool;
	CoroutineId                       m_coroutineId;
	CoroutineTaskPtr                  m_coroutineTask;
	CoroutineStatusEnum				  m_coroutineStatus;
	CoroutineContext                  m_context;
	ResumeReason                      m_resumeReason;
};

using CoroutinePtr = std::shared_ptr<Coroutine>;

#endif __Coroutine_H__
  • couroutine.cpp
#include "corountine.h"
#include "coroutine_task.h"
#include "coroutine_pool.h"

Coroutine::Coroutine(CoroutineId id, std::shared_ptr<CoroutinePool> coroutinePool) :
	m_coroutinePool(coroutinePool),
	m_coroutineId(id),
	m_coroutineTask(nullptr),
	m_coroutineStatus(CoroutineStatusEnum::COROUTINE_STATUS_UNKNOWN),
	m_context(nullptr),
	m_resumeReason(ResumeReason::REASON_NORMAL)
{
   
}

Coroutine::~Coroutine()
{
   
	unInitialize();
}

error_t Coroutine::initialize()
{
   
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值