各類纖程/協程使用比較
來源 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