纤程
纤程是Windows中的概念。在windows中一个线程可以有多个纤程,用户可以根据需要在各个纤程之间自由切换。
如果需要在某个线程中使用纤程,则必须先将该线程切换成纤程模式,这可以通过调用如下API函数实现:
LPVOID ConvertThreadToFiber(LPVOID lpParameter);
该函数可以将当前线程切换成纤程模式,也可以得到线程中的第一个纤程。我们可以通过函数的返回值来引用和操作纤程,这个纤程是线程的主纤程。但是这个主纤程无法指定纤程函数,所以什么也做不了。我们可以通过参数lpParameter向主纤程传递数据,使用如下API函数获取当前纤程的数据:
PVOID GetFiberData();
当在不同的纤程之间切换时,会涉及纤程上下面的切换,包括CPU寄存器的切换。在默认情况下,x86系统的CPU浮点状态信息不属于CPU寄存器,不会为每个纤程都维护一份,因此如果我们在我们的纤程中执行浮点操作,则会导致数据被破坏。为了禁用这种行为,需要使用如下函数:
LPVOID ConvertThreadToFiberEx(LPVOID lpParameter, DWORD dwFlags);
将第二个参数dwFlags设置为FIBER_FLAG_FLOAT_SWITCH即可。
将线程从纤程模式切换到默认的线程模式时,使用如下函数:
BOOL ConvertFiberToThread();
因为默认的主纤程什么都做不了,所以我们在需要时要创建新的线程,这时使用如下函数:
LPVOID CreateFiber(SIZE_T dwStackSize,
LPFIBER_START_ROUTINE lpStartAddress, LPVOID lpParameter);
参数dwStackSize指定纤程栈的大小,如果使用默认的大小,则将该值设置为0即可。我们可以将CreateFiber函数的返回值作为操作纤程的句柄。
启动纤程函数签名如下:
VOID WINAPI FIBER_START_ROUTINE(LPVOID lpFiberParameter);
当不需要纤程时,记得调用DeleteFiber删除纤程对象:
void DeleteFiber(LPVOID lpFiber);
在不同的纤程之间切换时,会用到API函数:
void SwitchToFiber(LPVOID lpFiber);
参数lpFiber即前面所说的纤程句柄。
和线程存在线程局部存储一样,纤程也可以有自己的局部存储—纤程局部存储,获取和设置纤程局部存储数据时,会用到API函数:
DWORD WINAPI FlsAlloc(PFLS_CALLBACK_FUNCTION lpCallback);
这个函数调用失败,则返回值是TLS_OUT_OF_INDEXES(0xFFFFFFFF);如果调用成功,则会得到一个索引。接下来就可以利用如下两个函数分别在这个索引指向的内存块中存储数据,或者在这个索引指向的内存块中取出数据了:
BOOL WINAPI FlsFree(DWORD dwFlsIndex);
BOOL WINAPI FlsSetValue(DWORD dwFlsIndex,PVOID lpFlsData);
当不需要索引指向的内存块时可以使用如下函数释放索引和内存块。
PVOID WINAPI FlsGetValue(DWORD dwFlsIndex);
纤程的本质来说是协程,Windows的纤程技术让单个线程能按用户的意愿像线程一样自由切换,且没有线程切换那样的开销和不可控性。
协程,可被认为是在应用层模拟的线程协程避免了线程上下面切换的部分额外损耗.同时具有并发运行的优点,降低了编写并发程序的复杂度。还是以上面的高并发网络服务器为例,可以为每个socket 连接都使用一个协程 来处理,在兼顾性能的情况下代码也清晰。
协程的概念早于线程提出,但它是非抢占式的调度,无法实现公平的任务调用,也无法直接利用多核CPU的优势,因此我们不能武断地说协程比线程更高级。目前主流的操作系统原生API并不支持协程技术,新兴的一些高级编程语言如Golang都是在语言的运行时环境中利用线程技术模拟了-套协程。