第十二章 纤程
使用纤程(fiber)
为了兼容UNIX服务器应用程序的移植。
使用纤程
线程是在内核模式下实现的,操作系统控制。
而纤程是在用户模式下实现的,内核对纤程一无所知。
一个线程可以包含一个或多个纤程。(内核仅能调度线程,而纤程由我们调度)
将一个已有的线程转换为纤程
WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
ConvertThreadToFiber(
_In_opt_ LPVOID lpParameter
);
这个函数会为纤程分配一个CONTEXT包含
1)一个用户自定义的值,它被初始化为传给ConvertThreadToFiber的lpParameter参数的值
2)结构化异常处理链的头
3)纤程栈的顶部和底部的内存地址(当我们将一个线程转换为纤程的时候,这时候也是线程栈)
4)某些CPU寄存器,包括栈指针,指令指针以及其他寄存器
默认情况下x86系统中CPU的浮点状态信息不属于CPU寄存器的一部分,不会为每个纤程都维护一份,而如果纤程要执行浮点操作,那会导致数据被破坏。
为了覆盖系统的默认行为需要使用新的ConvertThreadToFiberEx函数
允许传入FIBER_FLAG_FLOAT_SWITCH标志:
WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
ConvertThreadToFiberEx(
_In_opt_ LPVOID lpParameter,
_In_ DWORD dwFlags
);
除非打算在一个线程中执行多个纤程否则将线程转换为纤程没有任何意义。
WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
CreateFiber(
_In_ SIZE_T dwStackSize,
_In_ LPFIBER_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter
);
如果要使用大量的纤程,希望纤程消耗更少的内存使用下列函数来创建纤程。
WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
CreateFiberEx(
_In_ SIZE_T dwStackCommitSize,
_In_ SIZE_T dwStackReserveSize,
_In_ DWORD dwFlags,
_In_ LPFIBER_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter
);
dwStackReservesize 运行我们预定指定的虚拟内存。
dwFlags可以接受FIBER_FLAG_FLOAT_SWITCH将浮点运算状态保留
其他参数和CreateFiber相同
pfnStartAddress用来指定纤程函数的地址:
VOID WINAPI FiberFunc(PVOID pvParam);
但是CreateFiber创建的新纤程默认不会执行。(因为一个线程中只能执行一个纤程)
需要调用SwitchToFiber来切换纤程
WINBASEAPI
VOID
WINAPI
SwitchToFiber(
_In_ LPVOID lpFiber
);
1)将一些cpu寄存器目前的值,其中包括指令指针(IP)寄存器和栈指针寄存器(SP) 保存在当前运行的纤程的CONTEXT中
2)从即将运行的纤程的执行CONTEXT中载入先前保存的寄存器值并载入cpu
这些寄存器中包含栈指针寄存器,这样单线程继续执行就会使用新纤程的栈。
3)将新纤程的执行CONTEXT和线程关联,让线程运行指定的纤程。
4)将线程的指令指针设为先前保存的指令指针(IP),这样线程(纤程)就会从上次执行的地方开始继续往下执行。
SwitchToFiber是让纤程得到cpu时间的唯一方法。所以纤程的调度完全由我们自己掌控。
调用DeleteFiber来销毁纤程
WINBASEAPI
VOID
WINAPI
DeleteFiber(
_In_ LPVOID lpFiber
);
(TerminateThread不会销毁线程栈)
将转成了纤程的线程转回来。
ConvertTiberToThread(Ex)
如果为纤程保存一些信息可以使用纤程局部存储区(Fiber Local Storage, FLS)函数
其他纤程相关的函数
获得纤程执行的上下文
PVOID GetCurrentFiber();
返回纤程的用户自定义值(CreateFiber所传递的pvParam)
PVOID GetFiberData();
Counter示例程序
使用纤程来进行后台处理。
/******************************************************************************
Module: Counter.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h"
#include
#include
#include
#include "Resource.h"
//////////////////////////////////////////////////////////////////////////
// The possible state of the background processing
typedef enum {
BPS_STARTOVER, // Start the background processing from the beginning.
BPS_CONTINUE, // Continue the background processing.
BPS_DONE // There is no background processing to do.
} BKGNDPROCSTATE;
typedef struct {
PVOID pFiberUI; // User interface fiber execution context
HWND hwnd; // Handle of main UI window
BKGNDPROCSTATE bps; // State of background processing
} FIBERINFO, *PFIBERINFO;
// A global that contains application state information. This
// global is accessed directly by the UI fiber and indirectly
// by the background processing fiber.
FIBERINFO g_FiberInfo;
DWORD g_dwSlot = 0;
//////////////////////////////////////////////////////////////////////////
VOID WINAPI LogMessage(PVOID pFlsValue)
{
TCHAR szMsg[MAX_PATH];
// Check if we are in a fiber because this function can be called
// outside a fiber execution
if (IsThreadAFiber()) {
PVOID pFiberData = GetCurrentFiber();
PCTSTR pszFlsValue = (PCTSTR)FlsGetValue(g_dwSlot);
StringCchPrintf(szMsg, _countof(szMsg), TEXT("[0x%x - %s"),
pFiberData,
(pszFlsValue == NULL) ? TEXT("'Null value'") : (PCTSTR)pszFlsValue,
(pFlsValue == NULL) ? TEXT("'Null value'") : (PCTSTR)pFlsValue);
}
else {
StringCchCopy(szMsg, _countof(szMsg), TEXT("No more a fiber...\n"));
}
OutputDebugString(szMsg);
}
void WINAPI FiberFunc(PVOID pvParam) {
PFIBERINFO pFiberInfo = (PFIBERINFO)pvParam;
FlsSetValue(g_dwSlot, TEXT("Computation"));
LogMessage(TEXT("entering computation..."));
// Update the window showing which fiber is executing.
SetDlgItemText(pFiberInfo->hwnd, IDC_FIBER, TEXT("Recalculation"));
// Get the current count in the EDIT control.
int nCount = GetDlgItemInt(pFiberInfo->hwnd, IDC_COUNT, NULL, FALSE);
// Count from 0 to nCount, updating the STATIC control.
for (int x = 0; x <= nCount; x++) {
// UI events have higher priority than counting.
// If there are any UI events, handle them ASAP.
if (HIWORD(GetQueueStatus(QS_ALLEVENTS)) != 0) {
// The UI fiber has something to do; temporarily
// pause counting and handle the UI events.
SwitchToFiber(pFiberInfo->pFiberUI);
// The UI has no more events; continue counting.
SetDlgItemText(pFiberInfo->hwnd, IDC_FIBER, TEXT("Recalculation"));
}
// Update the STATIC control with the most recent count.
SetDlgItemInt(pFiberInfo->hwnd, IDC_ANSWER, x, FALSE);
// Sleep for a while to exaggerate the effect; remove
// the call to Sleep in production code.
Sleep(200);
}
// Indicate that counting is complete.
pFiberInfo->bps = BPS_DONE;
// Reschedule the UI thread. When the UI thread is running
// and has no events to process, the thread is put to sleep.
// NOTE: If we just allow the fiber function to return,
// the thread and the UI fiber die -- we don't want this!
SwitchToFiber(pFiberInfo->pFiberUI);
}
//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {
chSETDLGICONS(hwnd, IDI_COUNTER);
SetDlgItemInt(hwnd, IDC_COUNT, 0, FALSE);
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) {
switch (id) {
case IDCANCEL:
PostQuitMessage(0);
break;
case IDC_COUNT:
if (codeNotify == EN_CHANGE) {
// When the user changes the count, start the
// background processing over from the begining.
g_FiberInfo.bps = BPS_STARTOVER;
}
}
}
//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
}
return FALSE;
}
//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {
// Counter fiber execution context
PVOID pFiberCounter = NULL;
// Convert this thread to a fiber.
g_FiberInfo.pFiberUI = ConvertThreadToFiber(NULL);
g_dwSlot = FlsAlloc(LogMessage);
FlsSetValue(g_dwSlot, TEXT("UI fiber"));
// Create the application's UI window.
g_FiberInfo.hwnd = CreateDialog(hinstExe, MAKEINTRESOURCE(IDD_COUNTER),
NULL, Dlg_Proc);
// Update the window showing which fiber is executing.
SetDlgItemText(g_FiberInfo.hwnd, IDC_FIBER, TEXT("User interface"));
// Initially, there is no background processing to be done.
g_FiberInfo.bps = BPS_DONE;
// While the UI window still exists...
BOOL fQuit = FALSE;
while (!fQuit) {
// UI messages are higher priority than background processing.
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
// if a message exists in the queue, process it.
if (!IsDialogMessage(g_FiberInfo.hwnd, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
fQuit = (msg.message == WM_QUIT);
if (fQuit) {
// Release FLS slot
FlsFree(g_dwSlot);
// The background processing must be stopped.
if (pFiberCounter != NULL) {
// A recalculation fiber exists; delete it
DeleteFiber(pFiberCounter);
pFiberCounter = NULL;
}
// Quit the fiber mode and return to simple thread mode
ConvertFiberToThread();
g_FiberInfo.pFiberUI = NULL;
}
}
else {
// No UI msgs exist; check the state of the background processing.
switch (g_FiberInfo.bps) {
case BPS_DONE:
// No background processing to do; wait for a UI event.
WaitMessage();
break;
case BPS_STARTOVER:
// User changed the count;
// cancel the current background processing.
if (pFiberCounter != NULL) {
// A recalculation fiber exists; delete it so that
// background processing starts over from the begining.
DeleteFiber(pFiberCounter);
pFiberCounter = NULL;
}
// Conver this thread to a fiber if needed.
if (g_FiberInfo.pFiberUI == NULL)
g_FiberInfo.pFiberUI = ConvertThreadToFiber(NULL);
LogMessage(TEXT("convert UI thread to fiber..."));
// Create a new recalc fiber that starts from the beginning.
pFiberCounter = CreateFiber(0, FiberFunc, &g_FiberInfo);
// The background processing started; it should continue.
g_FiberInfo.bps = BPS_CONTINUE;
// Fall through to BPS_CONTINUE case...
case BPS_CONTINUE:
// Allow the background processing to execute...
SwitchToFiber(pFiberCounter);
// The background processing has been paused
// (because a UI message showed up or has been
// stopped (because the counting has completed).
// Update the window showing which fiber is executing.
SetDlgItemText(g_FiberInfo.hwnd, IDC_FIBER,
TEXT("User interface"));
if (g_FiberInfo.bps == BPS_DONE) {
// The background processing ran to completion. Delete the
// fiber so that processing will restart next time.
DeleteFiber(pFiberCounter);
pFiberCounter = NULL;
// Quit the fiber mode and return to simple thread mode
ConvertFiberToThread();
g_FiberInfo.pFiberUI = NULL;
}
break;
} // switch on background processing state
} // No UI messages exist
} // while the window still exists
DestroyWindow(g_FiberInfo.hwnd);
return 0; // End the application.
}