纖程本質上也是線程,是多任務系統的一部分,纖程為一個線程准並行方式調用多個不同函數提供了一種可能,它本身可以作為一種輕量級的線程使用。它與線程在本質上沒有區別,它也有上下文環境,纖程的上下文環境也是一組寄存器和調用堆棧。它是比線程更小的調度單位。注意一般我們認為線程是操作系統調用的最小單位,而纖程相比於線程來說更小,但是它是有程序員自己調用,而不由操作系統調用。系統在調度線程的時候會陷入到內核態,線程對象本身也是一種內核對象,而纖程完全是建立在用戶層上,它不是內核對象也沒有對象的句柄。通過纖程的機制實際就繞開了Windows的隨機調度線程執行的行為,調度算法由應用程序自己實現,這對一些並行算法非常有意義。因為纖程和線程本質上的類同性,所以也要按照理解線程為函數調用器的方式來理解纖程。
纖程的創建
纖程的創建需要必須建立在線程的基礎之上。在線程中調用函數ConvertThreadToFiber可以將一個線程轉化為纖程(或者說將一個線程與纖程綁定,以后可以將該纖程看做主纖程)。其他的纖程函數必須在纖程中調用,也就是說,如果目前在線程中,需要調用ConverThreadToFiber將線程轉化為纖程,才能調用對應的API。這個函數的原型如下:
LPVOID WINAPI ConvertThreadToFiber(
LPVOID lpParameter
);
這個函數傳入一個參數,類似於CreateThread函數中的線程函數參數,如果我們在主纖程中需要使用到它,可以使用宏GetFiberData取得這個參數。
在調用這個函數創建新纖程后,系統大概會給纖程分配200字節的棧空間,用來執行纖程函數,和保存纖程環境。這個環境由下面幾個部分的內容組成:
1. 用戶定義的值,這個值就是纖程回調函數中傳入的參數
2. 新的結構化異常處理的鏈表頭
3. 纖程內存棧的最高和最低地址,當線程轉換為纖程的時候,這也是線程的內存棧。之前說過纖程棧是在建立在線程的基礎之上,保留這兩個值是為了當纖程還原為線程后,用來還原線程棧環境
4. 各種CPU寄存器環境,相當於線程的CONTENT,但是沒有這個結構那么復雜,它只是保存了幾個簡單的寄存器的值。需要特別注意的一點是,它並沒有保存對應浮點數寄存器FPU的值,所以在纖程中使用浮點數計算可能會出現未知錯誤。如果一定要計算浮點數,那么可以使用ConverThreadToFiberEx,在第二個參數的位置傳入FIBER_FLAG_FLOAT_SWITCH值,表示將初始化並保存FPU。
可以在主纖程中調用CreateFiber函數創建子纖程。該函數原型如下:
LPVOID WINAPI CreateFiber(
DWORD dwStackSize,
LPFIBER_START_ROUTINE lpStartAddress,
LPVOID lpParameter
);
第一個參數是纖程的堆棧大小,默認給0的話,它會根據實際需求創建對應大小的堆棧,纖程的堆棧是建立在線程的基礎之上,我們可以這樣理解,它是從線程的堆棧中隔離一塊作為纖程的堆棧。本質上它的堆棧是放在線程的堆棧上。
第二個參數是一個回調,與線程函數類似,這個函數是一個纖程函數。
第三個參數是傳遞到回調函數中的參數。
函數CreateFiber 和 ConvertThreadToFiber 函數都返回一個void* 的指針,用來唯一標識一個纖程,在這我們可以將它理解為纖程的HANDLE .
纖程的刪除
當纖程結束時需要調用DeleteFiber來刪除線程,類似於CloseHandle來結束對應的內核對象。如果是調用轉化函數由線程轉化而來,調用DeleteFiber相當於調用ExitThread來終止線程,所以對於這種情況,最好是將纖程轉化為線程,然后再設計一套合理的線程退出機制。
纖程的調度
在任何一個纖程內部調用SwitchToFiber函數,將纖程的void*指針傳入,即可切換到對應的纖程,該函數可以在任意幾個纖程中進行切換,不管這些纖程是在一個線程中或者在不同的線程中。但是最好不要在不同線程中的纖程中進行切換,它可能會帶來意想不到的情況,假設存在這樣一種情況,線程A創建纖程FA,線程B創建纖程FB,當我們在系統運行線程A時將纖程從FA切換到FB,由於纖程的堆棧是建立在線程之上的,所以這個時候纖程B仍然使用線程A的堆棧,但是它應該使用的線程B的堆棧,這樣可能會對線程A的堆棧造成一定的破壞。
下面是纖使用的一個具體的例子:
#define PRIMARY_FIBER 0
#define WRITE_FIBER 1
#define READ_FIBER 2
#define FIBER_COUNT 3
#define COPY_LENGTH 512
VOID CALLBACK ReadFiber(LPVOID lpParam);
VOID CALLBACK WriteFiber(LPVOID lpParam);
typedef struct _tagFIBER_STRUCT
{
DWORD dwFiberHandle;
HANDLE hFile;
LPVOID lpParam;
}FIBER_STRUCT, *LPFIBER_STRUCT;
char *g_lpBuffer = NULL;
LPVOID g_lpFiber[FIBER_COUNT] = {};
void GetApp(LPTSTR lpPath, int nBufLen)
{
TCHAR szBuf[MAX_PATH] = _T("");
GetModuleFileName(NULL, szBuf, MAX_PATH);
int nLen = _tcslen(szBuf);
for(int i = nLen; i > 0; i--)
{
if(szBuf[i] == '\\')
{
szBuf[i + 1] = _T('\0');
break;
}
}
nLen = _tcslen(szBuf) + 1;
int nCopyLen = min(nLen, nBufLen);
StringCchCopy(lpPath, nCopyLen, szBuf);
}
int _tmain(int argc, _TCHAR* argv[])
{
g_lpBuffer = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, COPY_LENGTH);
FIBER_STRUCT fs[FIBER_COUNT] = {0};
TCHAR szDestPath[MAX_PATH] = _T("");
TCHAR szSrcPath[MAX_PATH] = _T("");
GetApp(szDestPath, MAX_PATH);
GetApp(szSrcPath, MAX_PATH);
StringCchCat(szSrcPath, MAX_PATH, _T("2.jpg"));
StringCchCat(szDestPath, MAX_PATH, _T("2_Cpy.jpg"));
HANDLE hSrcFile = CreateFile(szSrcPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
HANDLE hDestFile = CreateFile(szDestPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
fs[PRIMARY_FIBER].hFile = INVALID_HANDLE_VALUE;
fs[PRIMARY_FIBER].lpParam = NULL;
fs[PRIMARY_FIBER].dwFiberHandle = 0x00001234;
fs[WRITE_FIBER].hFile = hDestFile;
fs[WRITE_FIBER].lpParam = NULL;
fs[WRITE_FIBER].dwFiberHandle = 0x12345678;
fs[READ_FIBER].hFile = hSrcFile;
fs[READ_FIBER].dwFiberHandle = 0x78563412;
fs[READ_FIBER].lpParam = NULL;
g_lpFiber[PRIMARY_FIBER] = ConvertThreadToFiber(&fs[PRIMARY_FIBER]);
g_lpFiber[READ_FIBER] = CreateFiber(0, (LPFIBER_START_ROUTINE)ReadFiber, &fs[READ_FIBER]);
g_lpFiber[WRITE_FIBER] = CreateFiber(0, (LPFIBER_START_ROUTINE)WriteFiber, &fs[WRITE_FIBER]);
//切換到讀纖程
SwitchToFiber(g_lpFiber[READ_FIBER]);
//刪除纖程
DeleteFiber(g_lpFiber[WRITE_FIBER]);
DeleteFiber(g_lpFiber[READ_FIBER]);
CloseHandle(fs[READ_FIBER].hFile);
CloseHandle(fs[WRITE_FIBER].hFile);
//變回線程
ConvertFiberToThread();
return 0;
}
VOID CALLBACK ReadFiber(LPVOID lpParam)
{
//拷貝文件
while (TRUE)
{
LPFIBER_STRUCT pFS = (LPFIBER_STRUCT)lpParam;
printf("切換到[%08x]纖程\n", pFS->dwFiberHandle);
DWORD dwReadLen = 0;
ZeroMemory(g_lpBuffer, COPY_LENGTH);
ReadFile(pFS->hFile, g_lpBuffer, COPY_LENGTH, &dwReadLen, NULL);
SwitchToFiber(g_lpFiber[WRITE_FIBER]);
if(dwReadLen < COPY_LENGTH)
{
break;
}
}
SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}
VOID CALLBACK WriteFiber(LPVOID lpParam)
{
while (TRUE)
{
LPFIBER_STRUCT pFS = (LPFIBER_STRUCT)lpParam;
printf("切換到[%08x]纖程\n", pFS->dwFiberHandle);
DWORD dwWriteLen = 0;
WriteFile(pFS->hFile, g_lpBuffer, COPY_LENGTH, &dwWriteLen, NULL);
SwitchToFiber(g_lpFiber[READ_FIBER]);
if(dwWriteLen < COPY_LENGTH)
{
break;
}
}
SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}
上面這段代碼中首先將主線程轉化為主纖程,然后創建兩個纖程,分別用來讀文件和寫文件,然后保存這三個纖程。並定義了一個結構體用來向各個纖程函數傳入對應的參數。在主線程的后面首先切換到讀纖程,在讀纖程中利用源文件的句柄,讀入512字節的內容,然后切換到寫纖程,將讀到的這些內容寫回到磁盤的新文件中完成拷貝,然后切換到讀纖程,這樣不停的在讀纖程和寫纖程中進行切換,直到文件拷貝完畢。再切換回主纖程,最后在主纖程中刪除讀寫纖程,將主纖程轉化為線程並結束線程。