各类纤程/协程使用比较

Posted lsgxeva

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了各类纤程/协程使用比较相关的知识,希望对你有一定的参考价值。

各类纤程/协程使用比较

来源 https://blog.csdn.net/ruhailiu126/article/details/79691839

 

一:什么是纤程/协程?

   纤程(Fiber)是一种最轻量化的线程(lightweight threads)。它是一种用户线程(user thread),让应用程序可以独立决定自己的线程要如何运作。操作系统内核不能看见它,也不会为它进行调度。就像一般的线程,纤程有自己的寻址空间。但是纤程采取合作式多任务(Cooperative multitasking),而线程采取先占式多任务(Pre-emptive multitasking)。应用程序可以在一个线程环境中创建多个纤程,然后手动运行它。纤程不会被自动运行,必须要由应用程序自已指定让它运行,或换到下一个纤程。

 

二:为什么要使用纤程(纤程的优缺点)?

纤程的优点:

1. 消耗小,切换快,一个进程可以创建成千上万个纤程。

2. 小任务顺序编程很符合人的思维方式, 规避纯异步编程中状态机的复杂性. 使得使用纤程写的程序将更加的直观, 逻辑描述方便, 简化编程.纤程用于化异步为同步, 你可以进行一个异步操作以后就切换纤程,等到异步操作完成以后在切换回来,这样,在逻辑上相关的代码就可以写到一个函数里面,而不用人为的分到多个回调函数中。

3. 没有了线程所谓的安全问题, 避免锁机制

 

纤程的缺点:

1. 纤程一般只支持所有的纤程函数在一个线程里面跑. 无法充分利用多核CPU, 除非把所有的IO和计算操作都剥离成单独的线程。

2:关于跨平台的纤程的实现和使用资料较少。

 

三:如何理解纤程?

关于个人理解纤程的关键点:

1)关键点一:纤程用于执行流间的切换。

2)关键点二:纤程切换时,保存当前正在运行的纤程的上下文,恢复到被调用纤程的上下文。

纤程既然用于同一线程之间执行流的切换,为什么不直接采用回调函数调用了,回调函数调用,无法自动保存和恢复被调用函数的上下文信息。

 

四:关于纤程的windows下纤程的使用及举列子。

一、PVOID ConvertThreadToFiber(PVOID pvParam);

   调用这个函数之后,系统为纤程执行环境分配大概200字节的存储空间这个执行环境有以下内容构成:

1、用户定义的值,由参数pvParam参数指定。

2、结构化异常处理链头。

3、纤程内存栈的最高和最低地址,当线程转换为纤程的时候,这也是线程的内存栈。

4、各种CPU寄存器信息,比如堆栈指针寄存器,指令指针寄存器等等。

  默认情况下,x86系统的CPU的浮点数状态信息在纤程看来不属于CPU寄存器,因此会导致在纤程中执行一些相关的浮点运算会破坏数据。为了克服这个缺点,你需要呼叫ConvertThreadToFiberEx函数(Windows Vista及其以上版本中才有),并且传递FIBER_FLAG_FLOAT_SWITCH给它的第2个参数dwFlags。

如果一个线程中只有一个纤程,那么是没有必要将该线程转换为纤程的,只有你打算在同一个线程中再创建一个纤程才有转换的必要。要创建一个纤程,使用CreateFiber函数。

二 、 PVOID CreateFiber(DWORD dwStackSize , PFIBER_START_ROUTINE pfnStartAddress , PVOID pvParam):

   PVOID CreateFiber(     DWORD dwStackSize,  // 创建新的堆栈的大小,0表示默认大小    PFIBER_START_ROUTINE pfnStartAddress,   // 纤程函数地址     PVOID pvParam);     // 传递给纤程函数的参数

当CreateFiber(Ex)函数创建了一个新的堆栈之后,它分配一个新的纤程执行环境结构并初始化之,用户定义的数据通过pvParam参数被保存,新的堆栈的内存空间的最高和最低地址被保存,纤程函数的地址通过pStartAddress参数被保存。 纤程函数的格式必须如下定义:

       VOID WINAPI FiberFunc(PVOID pvParam);

这个纤程在第一次被调度的时候,纤程函数被调用,其参数pvParam由CreateFiber(Ex)中的pvParam参数指定。在纤程函数中,你可以做你想做的任何事情。像ConvertThreadToFiber(Ex)函数一样,CreateFiber(Ex)也返回纤程执行环境的内存地址,这个内存地址就像句柄一样,直接标识着一个纤程。

  当你使用CreateFiber(Ex)函数创建一个纤程之后,该纤程不会执行,因为系统不会自动调度它。你必须调用函数SwitchToFiber来告诉系统你想要哪个纤程执行:

三、SwitchToFiber函数内部的执行步骤如下: 1、保存当前的CPU寄存器信息,这些信息保存在正在运行的纤程的执行环境中。 2、从将要执行的纤程的执行环境中加载上次保存的CPU寄存器信息。 3、将即将执行的纤程执行环境与线程关联起来,由线程执行指定的纤程。 4、将指令指针设置为保存的值,继续上次的执行。

   SwitchToFiber函数是一个纤程能够被调度的唯一的方法,因此,纤程的调度是由用户完全操纵的。纤程的调度和线程的调度无关。一个线程,包含了正在运行的纤程,仍会被其他线程抢占。当一个线程被调度,而它里面有几个纤程,那么只有被选择的那个纤程才会执行,其他纤程的执行需要调用SwitchToFiber函数。

 

四、DeleteFiber函数一般是由一个纤程调用来删除另一个纤程。

   该函数首先清除纤程堆栈,然后删除纤程执行环境。但是,如果参数指定的是一个与当前线程关联的纤程,该函数呼叫ExitThread函数,线程结束,其包含的其他纤程也都结束。因此,DeleteFiber函数一般是由一个纤程调用来删除另一个纤程。

  当所有纤程结束了运行,你需要从纤程转换为线程,呼叫ConvertFiberToThread函数。

 

五、一个线程每次只能执行一个纤程,该纤程与这个线程相关联。

1、你可以使用如下函数来得到正在执行的纤程的执行环境内存地址,返回当前纤程的指针:

PVOID GetCurrentFiber();其实这是个宏

2、通过下函数获取当前纤程数据的指针:

GetFiberData();也是个宏

 

六、最后,让我们假设一个线程中有2个纤程,总结一下纤程的用法: 1、使用ConverThreadToFiber(Ex)将当前线程转换到纤程,这是纤程F1 2、定义一个纤程函数,用于创建一个新纤程:VOID CALLBACK FiberProc(); 3、纤程F1中调用CreateFiber(Ex)函数创建一个新的纤程F2 4、SwitchToFiber函数进行纤程切换,让新创建的纤程F2执行 5、F2纤程函数执行完毕的时候,使用SwitchToFiber转换到F1 6、在纤程F1中调用DeleteFiber来删除纤程F2 7、纤程F1中调用ConverFiberToThread,转换为线程 8、线程结束。

 

 五:关于跨平台纤程CN_FIBER。

 由于在LINUX 及MAC 平台下并没有现成的纤程函数,因此采用了boost库中协程(coroutine),经过多次测试选择了对称协程(symmetric coroutines)。关于boost库的非对称协称和对称协程的区别有以下描述:

In contrast to asymmetric coroutines, where the relationship between caller and callee is fixed, symmetric coroutines are able to transfer execution control to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required to return to its direct caller。

boost协程之间的跳转采用:

Class symmetric_coroutine<>::call_type

Class symmetric_coroutine<>::yield_type

 

为了跨平台代码移植的需要将boost协程封装为与windows基本一致的接口方式。

 

   相关接口定义如下: 其中CNFiberHandle用于标识fiber句柄。CNFiber类用于封装模拟window下的接口类。

#ifndef _CNFIBER_H
#define _CNFIBER_H
#pragma once
#include <boost/bind.hpp>
#include <boost/coroutine/all.hpp>
#include "CNBaseType.h"
#ifndef CN_WINDOWS
#define DLLEXPORT 
#define FUNCALL  __attribute__((stdcall))
#else
#define DLLEXPORT __declspec(dllexport) 
#define FUNCALL __stdcall
#endif
#define CNAPI
 
typedef void(FUNCALL *CNPFIBER_START_ROUTINE)(void* lpFiberParameter);
typedef CNPFIBER_START_ROUTINE CNLPFIBER_START_ROUTINE;
//typedef ZPVOID(CNAPI *CN_PFIBER_CALLOUT_ROUTINE)(ZPVOID lpParameter);
 
typedef boost::coroutines::coroutine< void >::pull_type pull_coro_t;
typedef boost::coroutines::coroutine< void >::push_type push_coro_t;
typedef  boost::coroutines::symmetric_coroutine<ZPVOID>::call_type  CN_COROUTINE_CALL_TYPE;
typedef boost::coroutines::symmetric_coroutine<ZPVOID>::yield_type  CN_COROUTINE_YIELD_TYPE;


typedef void(*CN_START_COROUTINE)(CN_COROUTINE_YIELD_TYPE& yield, ZPVOID pParam);
enum CN_ENUM_CORO_STATE
{
 IN_CN_THREAD=0,
 IN_CN_CORO  =1
};
class CNFiberHandle
{
public:
 CNFiberHandle()
 {
  m_pCoroutineCallType = NULL;
  m_pCoroutineYieldType = NULL;
  m_pFiberStartRoutine = NULL;
  m_lpParameter = NULL;
  m_CoroutineFlag = IN_CN_THREAD;


 };
 ~CNFiberHandle()
 {
 };
 void setCoroutineCallType(CN_COROUTINE_CALL_TYPE * pCoroutineCallType)
 {
  m_pCoroutineCallType = pCoroutineCallType;
 };
 void setCoroutineYieldType(CN_COROUTINE_YIELD_TYPE * pCoroutineYieldType)
 {
  m_pCoroutineYieldType = pCoroutineYieldType;
 };
 void setFiberStartRoutine(CNPFIBER_START_ROUTINE pFiberStartRoutine)
 {
  m_pFiberStartRoutine = pFiberStartRoutine;
 };
 void setParameter(ZPVOID pParameter)
 {
  m_lpParameter = pParameter;
 };
 void setCoroutineFlag(CN_ENUM_CORO_STATE CoroutineFlag )
 {
  m_CoroutineFlag = CoroutineFlag;
 }
 CN_COROUTINE_CALL_TYPE * getCoroutineCallType( )
 {
  return m_pCoroutineCallType;
 };
 CN_COROUTINE_YIELD_TYPE * getCoroutineYieldType()
 {
  return m_pCoroutineYieldType;
 };
 CNPFIBER_START_ROUTINE  getFiberStartRoutine()
 {
  return m_pFiberStartRoutine;
 };
 ZPVOID getParameter()
 {
  return m_lpParameter;
 };
 CN_ENUM_CORO_STATE getCoroutineFlag()
 {
  return m_CoroutineFlag;
 }


 // void fiberStartRoutine_w(CN_COROUTINE_YIELD_TYPE& coroutineYieldType, ZPVOID lpFiberParameter);
private:
 CN_COROUTINE_CALL_TYPE * m_pCoroutineCallType;
 CN_COROUTINE_YIELD_TYPE * m_pCoroutineYieldType;
 CNPFIBER_START_ROUTINE  m_pFiberStartRoutine;
 ZPVOID                      m_lpParameter;
 CN_ENUM_CORO_STATE          m_CoroutineFlag;
};


typedef void(CNAPI *CNPFIBER_START_ROUTINE_W)(CN_COROUTINE_YIELD_TYPE& coroutineYieldType, CNFiberHandle* pFiberHandle);
typedef CNPFIBER_START_ROUTINE_W CNLPFIBER_START_ROUTINE_W;
void fiberStartRoutine_w(CN_COROUTINE_YIELD_TYPE& coroutineYieldType, CNFiberHandle* pFiberHandle);
class CNFiber
{
public:
 CNFiber();
 ~CNFiber();
 //Converts the current thread into a fiber.
 ZPVOID ConvertThreadToFiber(ZPVOID lpParameter);
 
 //Allocates a fiber object, assigns it a stack, and sets up execution to begin at the specified start address, typically the fiber function.
 ZPVOID CNAPI CreateFiber(
  ZUINT32 dwStackSize,
  CNPFIBER_START_ROUTINE lpStartAddress,
  ZPVOID lpParameter
  );


 //Deletes a fiber.
 void CNAPI DeleteFiber(
  ZPVOID lpFiber
  );


 //Is an application - defined function used with the CreateFiber function.
    void  FiberProc(
  ZPVOID lpParameter
  );


 //Returns the address of the current fiber.
 ZPVOID GetCurrentFiber(void);
 //Returns the fiber data associated with the current fiber.
 ZPVOID CNAPI GetFiberData(void);
 //Schedules a fiber. The caller must be a fiber.
 void CNAPI SwitchToFiber(
  ZPVOID lpFiber
  );
private:
 void SetCurrentFiber(ZPVOID pFiber);
 
 
private:
 CNFiberHandle *m_pCurrentFiber;
};


#endif //_CNFIBER_H

 

 五:Tars协程。

     tars中的协程在用于任务调度时与以上介绍的协程使用上有所区别。在官方的举例中:


    1)   继承自Coroutine

    2)   重载实现handle。

    3)   /**

        * 初始化

        * @iNum, 表示要运行多个协程,即会有这个类多少个coroRun运行

        * @iTotalNum,表示这个线程最多包含的协程个数

        * @iStackSize,协程的栈大小

        */

    void setCoroInfo(uint32_t iNum, uint32_t iMaxNum, size_t iStackSize);

    调用 setCorolInfo 初始化相关信息。

    4)   Start 启动

    通过查看源码可以看到Coroutine继承自线程,handle相当于一般线程类中run函数。

    因此本质上是另外开启了1个线程,在开启的线程中启动了iNum个数的协程,协程执行的函数为handle函数(相互不影响)。

    由于目前并没有看到官方,协程与协程,协程与线程切换的使用样例,后续再补充。

================== End

 




以上是关于各类纤程/协程使用比较的主要内容,如果未能解决你的问题,请参考以下文章

协程及Python中的协程

关于进程线程协程管程纤程超线程的对比理解

python中的协程

nodejs中的fiber(纤程)库详解

C高级 跨平台协程库

Python学习之高级函数详解