12.1 纤程对象的介绍
(1)纤程与线程的比较
比较 |
线程(Thread) |
纤程(Fiber) |
实现方式 |
是个内核对象 |
在用户模式中实现的一种轻量级的线程,是比线程更小的调度单位。 |
调度方式 |
由Microsoft定义的算法来调度,操作系统对线程了如指掌。内核对线程的调度是抢占式的。 |
由我们自己调用SwitchToFiber来调度,内核对纤程一无所知。线程一次只能执行一个纤程代码,纤程间的调度不是抢占式的。 |
备注 |
①一个线程可以包含一个或多个纤程。操作系统随时可能夺走纤程所在线程的运行。当线程被调度时,当前被选择的纤程得以运行,而其他纤程无法运行,因为同一个线程中,每次只能有一个纤程正在运行,除非调用SwitchToFiber才能切换到另一个纤程去执行。与SwitchToThread不同,SwitchToFiber会立即切换到另一个纤程去执行(如果该线程的CPU时间还有剩余的话),而SwitchToThread要等CPU来调度另一个线程。 ②纤程与线程一样,也有自己的寄存器环境与函数调用栈。 |
(2)纤程的执行上下文的构成(类似线程上下文)——大约200个字节
①用户自定义的值,它被初始化为传给ConvertThreadToFiber的pvParam参数的值
②结构化异常处理链的头
③纤程栈顶部和底部的内存地址(当我们将一个线程转换为一个纤程时,这时也是线程栈)
④某些CPU寄存器,其中包括栈指针、指令指针以及其他寄存器(注意,默认下不包含CPU的浮点状态信息)
(3)纤程运行动态示意图
★注意:
在同一个线程里创建的两个纤程之间的切换是很安全的(如图中A箭头),但跨线程间的两个纤程的切换是不安全的(如图中的B、C箭头)。因为纤程本质上是由线程调度的,假设某个时刻,线程2正在调用纤程2.2,但在纤程2.2的内部调用了SwitchToFiber切换到了纤程1.2。如果CPU的下一个时间周期仍给线程2,因为内核并不知道纤程的切换,所以此时CPU仍会试图去执行纤程2.2的代码,但由于纤程的切换,会导致线程2的堆栈环境发生了变化,此时再去执行纤程2.2就可能会出现错误。
12.2 纤程的使用
(1)创建主纤程:CreateThreadToFiber(pvParam)(将已有线程转为纤程,该线程才能调用其它纤程API函数,可理解为启动线程的纤程模式)
★注意:
①返回值为纤程的上下文环境,可以理解为返回一个纤程对象。
②默认情况下,x86 CPU的FPU信息不会被纤悉无纤程保存下来,因此在进行浮点运算时,可能破坏数据。为避免此情况,要调该新的ConvertThreadToFiberEx函数,并为dwFlags传入FIBER_FLAG_FLOAT_SWITCH标志。
(2)创建纤程(可理解为子纤程):CreateFiber
参数 |
描述 |
DWORD dwStackSize |
纤程栈大小。一般传入0,表示系统自动分配 |
PFIBER_START_ROUTINE pfnStartAddress |
纤程函数,原型为 VOID WINAPI FiberFunc(PVOID pvParam) |
PVOID pvParam |
传给纤程函数的额外参数。 |
★注意:
①返回值为纤程的上下文环境,可以理解为返回一个纤程对象。
②同样,为防止发生浮点运算事故,可以调用新的API函数CreateFiberEx,并传入FIBER_FLAG_FLOAT_SWITCH标志。
(3)纤程的调度:SwitchToFiber(PVOID pvFiberExcutionContext)函数,其中的参数是CreateFiber或CreateThreadToFiber返回的纤程对象(即纤程上下文环境)。注意:SwitchToFiber是让纤程得到CPU时间的唯一方法!由于我们必须显示调用SwitchtoFiber来让纤程有机会得到执行,因此纤程的调度完全在我们的掌握之中。
①SwitchToFiber函数的内部运行
A.将一些CPU寄存器当前值(包括指令指针寄存器和栈指针寄存器),保存到当前正在运行的纤程的执行上下文中。
B.从即将运行的纤程的执行上下文中,将先前保存的寄存器载入CPU寄存器。使用当线程继续执行的时候,会使用新纤程的运行环境(如栈、指令指针)
C.将新纤程上下文与线程关联起来,让线程运行指定的纤程。
D.将线程的指令指针设为新纤程先前保存的指令指针,这样线程(纤程)就会从上次执行的地方开始继续往下执行。
(4)纤程的删除:DeleteFiber(PVOID pvFiberExecutionContext);
①当纤程执行结束后,调用该函数来销毁纤程,被删除的纤程的栈将被销毁,纤程执行的上下文也会被释放。
②如果纤程是ConvertThreadToFiber转换得到的主纤程,当调用DeleteFiber相当于调用ExitThread直接终止线程。如果不希望终止线程,可以调用ConvertFiberToThread将主纤程转回线程,这里也会释放原来调用ConverThreadToFiber将线程转化为纤程时所占用的最后一块内存。注意,ConvertFiberToThread只转换主纤程,对其它子纤程无效。
【Fiber程序】
#include <windows.h> #include <tchar.h> #include <strsafe.h> #include <locale.h> ////////////////////////////////////////////////////////////////////////// #define QM_ALLOC(sz) HeapAlloc(GetProcessHeap(),0,sz) #define QM_CALLOC(sz) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sz) #define QM_SAFEFREE(p) if(NULL !=p){HeapFree(GetProcessHeap(),0,p);p=NULL;} ////////////////////////////////////////////////////////////////////////// #define BUFFER_SIZE 32768 //32*1024,即32K #define FIBER_COUNT 3 //最大的纤程数(包含主纤程) #define PRIMARY_FIBER 0 //主纤程的索引 #define READ_FIBER 1 //读纤程的索引 #define WRITE_FIBER 2 //写纤程的索引 #define RTN_OK 0 //RTN =Return #define RTN_USAGE 1 #define RTN_ERROR 13 ////////////////////////////////////////////////////////////////////////// LPVOID g_lpFiber[FIBER_COUNT]; LPBYTE g_lpBuffer; DWORD g_dwBytesRead; //分批读取的字节数,要在读和写纤程中共享这个变量 ////////////////////////////////////////////////////////////////////////// typedef struct{ DWORD dwParamter; //DWORD parameter to Fiber(unnsed) DWORD dwFiberResultCode; //GetLastError result code HANDLE hFile; //handle to operate on DWORD dwBytesProcessed; //number of bytes to processed }FIBERDATASTRUCT,*PFIBERDATASTRUCT,*LPFIBERDATASTRUCT; VOID DisplayFiberInfo(void); VOID WINAPI ReadFiberFunc(LPVOID lpParameter); VOID WINAPI WriteFiberFunc(LPVOID lpParameter); ////////////////////////////////////////////////////////////////////////// __inline VOID GetAppPath(LPTSTR pszBuffer){ DWORD dwLen = 0; if (0 == (dwLen = GetModuleFileName(NULL, pszBuffer, MAX_PATH))){ return; } DWORD i = dwLen; for (; i > 0;i--){ if ('\'==pszBuffer[i]){ pszBuffer[i + 1] = '