各類纖程/協程使用比較


各類纖程/協程使用比較

來源 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM