《Windows via C/C++》學習筆記 —— 纖程(Fiber)

引用自:http://www.cnblogs.com/wz19860913/archive/2008/08/26/1276816.html

 

  纖程(Fiber),是微軟加入到Windows中,使得UNIX服務器應用程序更好地移植到Windows中。所以本篇真正沒有多少應用價值,只是爲了使得筆記更加完整。

 

  看完本章,感覺纖程是比線程的更小的一個運行單位。可以把一個線程拆分成多個纖程,然後通過人工轉換纖程,從而讓各個纖程工作。

  要知道的是人工的轉換,不是系統自動切換。因爲線程的實現通過Windows內核完成的,因此Windows可以自動對線程進行調度。但是纖程是通過用戶模式的代碼來實現的,是程序員自己寫的算法,內核不知道纖程的實現方式,而是你自己定義的調度算法,因此纖程是“非搶佔”的調度方式。

  還有要知道就是,一個線程可以包含多個纖程。

 

  要使用纖程,首先要做的就是把當前線程轉換爲纖程:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->PVOID ConvertThreadToFiber(PVOID pvParam);

 

 

  調用這個函數之後,系統爲纖程執行環境分配大概200字節的存儲空間,這個執行環境有以下內容構成:

1、用戶定義的值,由參數pvParam參數指定。

2、結構化異常處理鏈頭。

3、纖程內存棧的最高和最低地址,當線程轉換爲纖程的時候,這也是線程的內存棧。

4、各種CPU寄存器信息,比如堆棧指針寄存器,指令指針寄存器等等。

 

  默認情況下,x86系統的CPU的浮點數狀態信息在纖程看來不屬於CPU寄存器,因此會導致在纖程中執行一些相關的浮點運算會破壞數據。爲了克服這個缺點,你需要呼叫ConvertThreadToFiberEx函數(Windows Vista及其以上版本中才有),並且傳遞FIBER_FLAG_FLOAT_SWITCH給它的第2個參數dwFlags:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->PVOID ConvertThreadToFiberEx(
   PVOID pvParam,
   DWORD dwFlags);

 

 

  當呼叫完上述兩個函數之後,你就初始化了一個纖程執行環境,該執行環境與線程的執行環境關聯,線程轉換爲纖程,纖程就在線程的內部運行。ConvertThreadToFiber(Ex)函數實際返回纖程的執行環境的內存地址,你稍後會用到這個地址,但是你不能直接讀取或寫入這個地址,你應該使用系統提供的纖程函數來對這個地址進行操縱。

  當你的纖程返回或者呼叫ExitThread的時候,你的纖程也隨之結束。

 

  如果一個線程中只有一個纖程,那麼是沒有必要將該線程轉換爲纖程的,只有你打算在同一個線程中再創建一個纖程纔有轉換的必要。要創建一個纖程,使用CreateFiber函數:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->PVOID CreateFiber(
   DWORD dwStackSize,     
// 創建新的堆棧的大小,0表示默認大小
   PFIBER_START_ROUTINE pfnStartAddress,     // 纖程函數地址
   PVOID pvParam);     // 傳遞給纖程函數的參數

 

 

  這個函數創建一個新的堆棧,堆棧的大小由dwStackSize指定。如果傳遞0給它,就意味着創建一個默認大小的堆棧。

  如果你打算讓一個線程包含多個纖程,而又想花費比較少的空間的話,可以使用CreateFiberEx函數(只有在Windows Vista及其以上版本中才有):

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->PVOID CreateFiberEx(
   SIZE_T dwStackCommitSize,     
// 堆棧初始提交的大小
   SIZE_T dwStackReserveSize,    // 需要保留的虛擬內存的大小
   DWORD dwFlags,     // 創建旗標
   PFIBER_START_ROUTINE pStartAddress,     // 纖程函數指針
   PVOID pvParam);     // 傳遞給纖程函數的參數

 

 

  其中,如果傳遞FIBER_FLAG_FLOAT_SWITCH給dwFlags參數,則表明將浮點信息添加到纖程執行環境。

 

  當CreateFiber(Ex)函數創建了一個新的堆棧之後,它分配一個新的纖程執行環境結構並初始化之,用戶定義的數據通過pvParam參數被保存,新的堆棧的內存空間的最高和最低地址被保存,纖程函數的地址通過pStartAddress參數被保存。

  纖程函數的格式必須如下定義:

VOID WINAPI FiberFunc(PVOID pvParam);

  這個纖程在第一次被調度的時候,纖程函數被調用,其參數pvParam由CreateFiber(Ex)中的pvParam參數指定。在纖程函數中,你可以做你想做的任何事情。

  像ConvertThreadToFiber(Ex)函數一樣,CreateFiber(Ex)也返回纖程執行環境的內存地址,這個內存地址就像句柄一樣,直接標識着一個纖程。

  當你使用CreateFiber(Ex)函數創建一個纖程之後,該纖程不會執行,因爲系統不會自動調度它。你必須調用函數SwitchToFiber來告訴系統你想要哪個纖程執行:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->VOID SwitchToFiber(PVOID pvFiberExecutionContext);

 

 

  SwitchToFiber函數的參數是一個纖程執行環境的內存地址,該地址由ConverThreadToFiber(Ex)或CreateFiber(Ex)返回。

  SwitchToFiber函數內部的執行步驟如下:

1、保存當前的CPU寄存器信息,這些信息保存在正在運行的纖程的執行環境中。

2、從將要執行的纖程的執行環境中加載上次保存的CPU寄存器信息。

3、將即將執行的纖程執行環境與線程關聯起來,由線程執行指定的纖程。

4、將指令指針設置爲保存的值,繼續上次的執行。

 

  SwitchToFiber函數是一個纖程能夠被調度的唯一的方法,因此,纖程的調度是由用戶完全操縱的。纖程的調度和線程的調度無關。一個線程,包含了正在運行的纖程,仍會被其他線程搶佔。當一個線程被調度,而它裏面有幾個纖程,那麼只有被選擇的那個纖程纔會執行,其他纖程的執行需要調用SwitchToFiber函數。

 

  最後,如果一個纖程完成了任務,你需要刪除它,呼叫DeleteFiber函數,並傳遞這個纖程的執行環境內存地址:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->VOID DeleteFiber(PVOID pvFiberExecutionContext);

 

 

   該函數首先清除纖程堆棧,然後刪除纖程執行環境。但是,如果參數指定的是一個與當前線程關聯的纖程,該函數呼叫ExitThread函數,線程結束,其包含的其他纖程也都結束。因此,DeleteFiber函數一般是由一個纖程調用來刪除另一個纖程。

  當所有纖程結束了運行,你需要從纖程轉換爲線程,呼叫ConvertFiberToThread函數。

 

  如果你需要在纖程中保存一些數據,可以使用“纖程局部存儲”(FLS)的機制。這個機制和“線程局部存儲”(TLS)類似。

  首先,呼叫FlsAlloc函數分配FLS槽來存放數據,這個FLS槽可以被當前進程內所有纖程共同使用,函數有一個參數:一個回調函數指針,這個回調函數會在以下兩種情況下被調用:一個纖程被刪除;FLS槽通過FlsFree函數被刪除。

  然後,在你呼叫FlsAlloc函數之後,你可以在纖程中使用FlsSetValue函數來保存數據到FLS槽中,同時該函數需要一個DWORD類型的參數,表示一個FLS槽的索引,即在FLS槽的相關地方保存數據。

  接着,你可以在各個纖程中使用FlsGetValue函數來取得FLS槽中對應的數據,同樣需要上面那個FLS槽索引,並返回指向數據的指針。

  當使用完這些數據之後,你可以使用FlsFree來釋放FLS槽。

 

  如果你想知道你是否正在一個纖程執行環境中運行,可以使用IsThreadAFiber函數,它返回一個BOOL值,指明你是否正在一個纖程中運行。

 

  一個線程每次只能執行一個纖程,該纖程與這個線程相關聯。你可以使用如下函數來得到正在執行的纖程的執行環境內存地址:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->PVOID GetCurrentFiber();

 

 

  每個纖程包含用戶定義的一個數據,這個數據由CreateFiber(Ex)或ConvertThreadToFiber(Ex)的pvParam參數指定,你可以使用如下函數得到這個數據的指針:

 

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->PVOID GetFiberData();

 

 

  最後,讓我們假設一個線程中有2個纖程,總結一下纖程的用法:

1、使用ConverThreadToFiber(Ex)將當前線程轉換到纖程,這是纖程F1

2、定義一個纖程函數,用於創建一個新纖程

3、纖程F1中調用CreateFiber(Ex)函數創建一個新的纖程F2

4、SwitchToFiber函數進行纖程切換,讓新創建的纖程F2執行

5、F2纖程函數執行完畢的時候,使用SwitchToFiber轉換到F1

6、在纖程F1中調用DeleteFiber來刪除纖程F2

7、纖程F1中調用ConverFiberToThread,轉換爲線程

8、線程結束

 

發佈了15 篇原創文章 · 獲贊 8 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章