【并发编程十九】芊程(fiber)
简介:
我们本篇先讲解下芊程,下一篇再介绍协程,因为有了芊程的概念后,我们再讲解协程,就好理解了。
一、前言
芊程(fiber)是windows系统中的概念。当我们需要异步执行一些任务时,常见的作法就是开启一个工作线程,在工作线程中执行我们的任务。但是这样存在两个问题:
- 对于线程的调度是由操作系统内核控制的,所以我们无法确认操作系统何时会运行或挂起该线程。
- 对于一些轻量级的任务,创建一个新的线程去做,消耗较大。
那么有没有一种机制,既能新建线程执行任务,又没有新建线程消耗那么大呢?有,这就是芊程。
二、芊程(fiber)
1、线程中使用芊程
在windows系统中,一个线程可以有多个芊程,用户可以根据需要在各个芊程之间自由切换。如果需要在某个线程中使用芊程,则必须将该线程切换成芊程模式,这可以通过调用如下API函数实现。
PVOID ConvertThreadToFiber(LPVOID lpParameter)
这个函数不仅仅将当前线程切换成芊程模式,也可以得到线程中的第1个芊程。我们可以通过这个函数的返回值来引用和操作芊程,这个线程是线程中的主芊程。但是这个主芊程无法指定芊程函数,所以什么也做不了。
简单了解:
在不同的芊程之间切换时,也会涉及到芊程上下面的切换,包括cpu寄存器数据的切换。默认情况下,x86系统的cpu浮点状态信息不属于cpu寄存器,不会为每个芊程都维护一份,因此如果在我们的芊程中执行浮点操作,则会导致数据被破坏。为了禁用这种行为,我需要用到convertThreadToFiberEx 函数
LPVOID ConvertThreadToFiberEx(
[in, optional] LPVOID lpParameter,
[in] DWORD dwFlags
);
2、获取当前芊程数据
我们可以通过参数lpParameter向主芊程传递数据,使用如下API函数获取当前芊程的数据。
PVOID GetFiberData();
3、从芊程切回线程
将线程从芊程模式切回至默认的线程模式时,会调用API函数
BOOL ConvertFiberToThread();
4、创建新的芊程
因为默认的主芊程什么都做不了,所以我们在需要时要创建新的芊程,这会用到API函数
LPVOID CreateFiber(
[in] SIZE_T dwStackSize,
[in] LPFIBER_START_ROUTINE lpStartAddress,
[in, optional] LPVOID lpParameter
);
和创建线程的函数类似,参数dwStackSize指定芊程栈的大小,如果使用默认大小,这该值设置为0即可。我们可以将CreateFiber函数的返回值作为操作芊程的句柄。
芊程函数签名如下
VOID LPFIBER_START_ROUTINE(LOVOID lpParameter)
5、删除芊程对象
当不需要使用芊程时,需要调用DeleteFiber删除芊程对象
void DeleteFiber(
[in] LPVOID lpFiber
);
6、在不同芊程间切换
在不通过的芊程之间切换时,会调用API函数
void SwitchToFiber([in] LPVOID lpFiber);
7、芊程局部存储
和线程存在线程局部存储一样,芊程也可以有自己的局部存储——芊程局部存储,获取和设置芊程局部存储时,会调用API函数
DWORD FlsAlloc(
[in] PFLS_CALLBACK_FUNCTION lpCallback
);
BOOL FlsFree(
[in] DWORD dwFlsIndex
);
BOOL FlsSetValue(
[in] DWORD dwFlsIndex,
[in, optional] PVOID lpFlsData
);
PVOID FlsGetValue(
[in] DWORD dwFlsIndex
);
三、demo
以下代码只有一个主线程,主线程在切换到新建的芊程SwitchToFiber。由于在新建的芊程函数中是一个while无限循环,所以,如果协程中如果没有SwitchToFiber切回主协程,代码将在while中无限循环下去。
- 代码
#include <iostream>
#include<Windows.h>
#include<string>
using namespace std;
LPVOID mainWorkerFiber = NULL;
LPVOID pWorkerFiber = NULL;
void WINAPI workerFiberProc(LPVOID lpFiberParameter)
{
while (true)
{
// 假设这是一个很耗时的操作
SYSTEMTIME st;
GetLocalTime(&st);
cout << st.wYear << "-" << st.wMonth << "-" << st.wDay << " " << st.wHour << ":" << st.wMinute << ":" << st.wSecond <<" "<<st.wMilliseconds<< endl;
//切回主协程
SwitchToFiber(mainWorkerFiber);
}
}
int main()
{
mainWorkerFiber = ConvertThreadToFiber(NULL);
int index = 0;
while (index < 10)
{
++index;
pWorkerFiber = CreateFiber(0, workerFiberProc, NULL);
if (NULL == pWorkerFiber)
return - 1;
cout << "time not set"<<endl;
Sleep(2000);
SwitchToFiber(pWorkerFiber);
}
DeleteFiber(pWorkerFiber);
ConvertFiberToThread();
return 0;
}
- 切回主协程SwitchToFiber未注释掉,输出如下
- 切回主协程SwitchToFiber注释掉,输出如下
四、芊程和协程
- 芊程从本质上来说就是协程(coroutine),Windows的芊程技术让单个线程能按照用户的意愿像线程一样自由切换,且没有线程切换那么大的开销和不可控性。
参考:
1、《c++服务器开发精髓》 张远龙 著;;
2、微软官网:https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createfiber