windows纤程(Fiber)机制
Windows 10(17763)系统上执行tjInitCompress函数时,加载dll失败,从而导致崩溃
KERNELBASE.dll pc 0x69 RaiseException (:0)[amd64:Windows NT:7BF8014B3D39BE6ADDC83DA4991008EB1] MyGame.exe pc 0x6b517c0 _delayLoadHelper2(ImgDelayDescr const*, long long (**)()) (D:\a\_work\1\s\src\vctools\delayimp/delayhlp.cpp:312)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x6aa6669 tjInitCompress[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x3ea56a0 FJpegImageWrapper::FJpegImageWrapper(int) (E:\UnrealEngine\Engine\Source\Runtime\ImageWrapper\Private\Formats/JpegImageWrapper.cpp:68)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x3ea1db3 FImageWrapperModule::CreateImageWrapper(const EImageFormat) (E:\UnrealEngine\Engine\Source\Runtime\ImageWrapper\Private/ImageWrapperModule.cpp:75)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x5223d82 UAsyncTaskDownloadImage::HandleImageRequest(TSharedPtr, TSharedPtr, bool) (E:\UnrealEngine\Engine\Source\Runtime\UMG\Private\Blueprint/AsyncTaskDownloadImage.cpp:115)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x18805a2 TBaseUObjectMethodDelegateInstance<0,UAsyncTaskDownloadFile,void __cdecl(TSharedPtr,TSharedPtr,bool),FDefaultDelegateUserPolicy>::ExecuteIfSafe(TSharedPtr, TSharedPtr, bool) const (E:\UnrealEngine\Engine\Source\Runtime\Core\Public\Delegates/DelegateInstancesImpl.h:611)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x4cacee4 TDelegate,TSharedPtr,bool),FDefaultDelegateUserPolicy>::ExecuteIfBound(TSharedPtr, TSharedPtr, bool) const (E:\UnrealEngine\Engine\Source\Runtime\Core\Public\Delegates/DelegateSignatureImpl.inl:599)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x4cb297c FCurlHttpRequest::FinishedRequest() (E:\UnrealEngine\Engine\Source\Runtime\Online\HTTP\Private\Curl/CurlHttp.cpp:1274)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x4cbd8df FHttpManager::Tick(float) (E:\UnrealEngine\Engine\Source\Runtime\Online\HTTP\Private/HttpManager.cpp:255)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x39c00d6 FTicker::Tick(float) (E:\UnrealEngine\Engine\Source\Runtime\Core\Private\Containers/Ticker.cpp:92)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x39a9515 static UE4Function_Private::TFunctionRefCaller<<lambda_6153fdc59763e04cd81e0809850c9c28>,bool __cdecl(float)>::Call(void*, float &) (E:\UnrealEngine\Engine\Source\Runtime\Core\Public\Templates/Function.h:539)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x39bdd8f TBaseFunctorDelegateInstance >::Execute(float) const (E:\UnrealEngine\Engine\Source\Runtime\Core\Public\Delegates/DelegateInstancesImpl.h:831)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x39c00d6 FTicker::Tick(float) (E:\UnrealEngine\Engine\Source\Runtime\Core\Private\Containers/Ticker.cpp:92)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0xafdf2b FEngineLoop::Tick() (E:\UnrealEngine\Engine\Source\Runtime\Launch\Private/LaunchEngineLoop.cpp:5454)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0xb0ad7c GuardedMain(wchar_t const*) (E:\UnrealEngine\Engine\Source\Runtime\Launch\Private/Launch.cpp:200)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0xb0adea GuardedMainWrapper(wchar_t const*) (E:\UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows/LaunchWindows.cpp:144)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0xb164cd WinMain(HINSTANCE__ *, HINSTANCE__ *, char*, int) (E:\UnrealEngine\Engine\Source\Runtime\Launch\Private\Windows/LaunchWindows.cpp:339)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] MyGame.exe pc 0x6b52e42 __scrt_common_main_seh() (D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup/exe_common.inl:288)[amd64:Windows NT:79DC3D8E2E0D4CD6B1F68294B5AACE771] kernel32.dll pc 0x14 BaseThreadInitThunk (:0)[amd64:Windows NT:7C073461BC41EEA8F242AD876C2F4A9F1] ntdll.dll pc 0x21 RtlUserThreadStart (:0)[amd64:Windows NT:2055091C8F2C5808D8DFE02C75D129591]
具体代码如下:
具体原因是tjInitCompress函数中会调用FlsAlloc进行fls分配,而fls分配次数超过了系统限制的128个,从而导致崩溃
00 0000003a`bbfff0b8 00007fff`1b72e7a5 KERNELBASE!FlsAlloc 01 0000003a`bbfff0c0 00007fff`1b72b1db turbojpeg!Java_org_libjpegturbo_turbojpeg_TJDecompressor_destroy+0x19fb5 02 0000003a`bbfff0f0 00007fff`1b72b36b turbojpeg!Java_org_libjpegturbo_turbojpeg_TJDecompressor_destroy+0x169eb 03 0000003a`bbfff120 00007fff`abb18b7f turbojpeg!Java_org_libjpegturbo_turbojpeg_TJDecompressor_destroy+0x16b7b 04 0000003a`bbfff160 00007fff`abb5d51d ntdll!LdrpCallInitRoutine+0x6b 05 0000003a`bbfff1d0 00007fff`abb5d2ce ntdll!LdrpInitializeNode+0x1c9 06 0000003a`bbfff320 00007fff`abb1db0d ntdll!LdrpInitializeGraphRecurse+0x42 07 0000003a`bbfff360 00007fff`abb18e20 ntdll!LdrpPrepareModuleForExecution+0xc5 08 0000003a`bbfff3a0 00007fff`abb090ac ntdll!LdrpLoadDllInternal+0x20c 09 0000003a`bbfff440 00007fff`abb1a73a ntdll!LdrpLoadDll+0xb0 0a 0000003a`bbfff600 00007fff`a923b412 ntdll!LdrLoadDll+0xfa 0b 0000003a`bbfff6f0 00007fff`a92374b1 KERNELBASE!LoadLibraryExW+0x172 0c 0000003a`bbfff760 00007fff`2243c42f KERNELBASE!LoadLibraryExA+0x31 0d 0000003a`bbfff7a0 00007ff6`8090de0e EOSOVH_Win64_Shipping+0x3c42f 0e 0000003a`bbfff7e0 00007ff6`80822b61 UAGame!__delayLoadHelper2+0x14a [D:\a\_work\1\s\src\vctools\delayimp\delayhlp.cpp @ 285] 0f 0000003a`bbfff880 00007ff6`7a72da10 UAGame!_tailMerge_turbojpeg_dll+0x3f 10 0000003a`bbfff8f0 00007ff6`7a7291c3 UAGame!FJpegImageWrapper::FJpegImageWrapper+0x50 [G:\CAEngine\Engine\Source\Runtime\ImageWrapper\Private\Formats\JpegImageWrapper.cpp @ 68] 11 0000003a`bbfff920 00007ff6`78fe9aa5 UAGame!FImageWrapperModule::CreateImageWrapper+0xc3 [G:\CAEngine\Engine\Source\Runtime\ImageWrapper\Private\ImageWrapperModule.cpp @ 75] 12 0000003a`bbfff950 00007ff6`78fa675e UAGame!UImageLoader::LoadImageWrapperFromDisk+0x185 [G:\CAGame\Source\UACommon\Private\ImageLoader.cpp @ 154] 13 (Inline Function) --------`-------- UAGame!UImageLoader::LoadImageWrapperFromDiskInThreadPool::__l2::<lambda_e43cdf58c4a4f1b2f6587ee8f005a51d>::operator()+0x5 [G:\CAGame\Source\UACommon\Private\ImageLoader.cpp @ 128] 14 (Inline Function) --------`-------- UAGame!Invoke+0x5 [G:\CAEngine\Engine\Source\Runtime\Core\Public\Templates\Invoke.h @ 51] 15 0000003a`bbfffa00 00007ff6`78f9c277 UAGame!UE4Function_Private::TFunctionRefCaller<<lambda_e43cdf58c4a4f1b2f6587ee8f005a51d>,TSharedPtr<IImageWrapper,0> __cdecl(void)>::Call+0xe [G:\CAEngine\Engine\Source\Runtime\Core\Public\Templates\Function.h @ 539] 16 (Inline Function) --------`-------- UAGame!UE4Function_Private::TFunctionRefBase<UE4Function_Private::TFunctionStorage<1>,TSharedPtr<IImageWrapper,0> __cdecl(void)>::operator()+0x27 [G:\CAEngine\Engine\Source\Runtime\Core\Public\Templates\Function.h @ 676] 17 0000003a`bbfffa30 00007ff6`78fb23a6 UAGame!SetPromise<TSharedPtr<IImageWrapper,0>,TUniqueFunction<TSharedPtr<IImageWrapper,0> __cdecl(void)> &>+0x37 [G:\CAEngine\Engine\Source\Runtime\Core\Public\Async\Async.h @ 50] 18 0000003a`bbfffa80 00007ff6`79b1a058 UAGame!TAsyncQueuedWork<TSharedPtr<IImageWrapper,0> >::DoThreadedWork+0x16 [G:\CAEngine\Engine\Source\Runtime\Core\Public\Async\Async.h @ 224] 19 0000003a`bbfffab0 00007ff6`79fcdbdb UAGame!FQueuedThread::Run+0xc18 [G:\CAEngine\Engine\Source\Runtime\Core\Private\HAL\ThreadingBase.cpp @ 1020] 1a 0000003a`bbfffd20 00007ff6`79fc664a UAGame!FRunnableThreadWin::Run+0x4b [G:\CAEngine\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp @ 90] 1b 0000003a`bbfffd50 00007fff`aa3b257d UAGame!FRunnableThreadWin::GuardedRun+0x1aa [G:\CAEngine\Engine\Source\Runtime\Core\Private\Windows\WindowsRunnableThread.cpp @ 27] 1c 0000003a`bbfffd90 00007fff`abb4af28 KERNEL32!BaseThreadInitThunk+0x1d 1d 0000003a`bbfffdc0 00000000`00000000 ntdll!RtlUserThreadStart+0x28
纤程(Fiber)
Fiber是Windows提供的用户级线程的,类似于协程(Coroutine)的概念。
Fiber在单线程中非常有用,多个Fiber运行在同一个线程(Thread)中,当前Thread执行那个Fiber需要开发者主动控制,Fiber调度模块只负责提供堆栈,环境、寄存器等上下文的保存,不负责分配时间片等。
我们可以用ConvertThreadToFiber把一个Thread 转换成一个Fiber ,也可以调用CreateFiber创建一个纤程,但并不切换过去运行。
被创建出来的 Fiber 会有一个上下文的地址被返回,用于以后的切换操作。切换Fiber需要用SwitchToFiber函数,这是唯一用于 Fiber 释放操作权的途径。SwitchToFiber必须显式的指定切换的目标,所以Fiber调度的工作需要开发者写代码来实现。
GetCurrentFiber 和 GetFiberData 这两个函数都很有用,一个用来取到运行环境,一个用来取得创建参数,这两个函数都是用inline函数的形式提供在 .h 文件中的。
注1:每个Fiber都有自己的Callstack,大小缺省为1M,在Visual Studio中,可以看到当前进程中每个线程的Callstack,但没法看到每个Fiber的Callstack,这会给调试带来困难
注2:一个线程可以包含一个或多个纤程。操作系统随时可能夺走纤程所在线程的运行。当线程被调度时,当前被选择的纤程得以运行,而其他纤程无法运行
因为同一个线程中,每次只能有一个纤程正在运行,除非调用SwitchToFiber才能切换到另一个纤程去执行。
与切换Thread不同,SwitchToFiber会立即切换到另一个纤程去执行(如果该线程的CPU时间还有剩余的话),而切换Thread要等CPU来调度另一个线程。
# |
线程(Thread) |
纤程(Fiber) |
实现方式 |
是个内核对象 |
在用户模式中实现的一种轻量级的线程,是比线程更小的调度单位。 |
调度方式 |
由Microsoft定义的算法来调度,操作系统对线程了如指掌。内核对线程的调度是抢占式的。 |
由开发者自己调用SwitchToFiber来调度,内核对纤程一无所知。线程一次只能执行一个纤程代码,纤程间的调度不是抢占式的。 |
FLS(Fiber Local Storage,纤程局部存储)
FLS是类似于TLS(Thread Local Storage,线程局部存储)的数据结构,每个Fiber都会有自己FLS数据副本,互不干扰。
(1) 使用FLS的步骤如下:
①调用FlsAlloc分配FLS索引
②调用FlsSetValue将Fiber的值写入该索引FLS
③调用FlsGetValue取得Fiber存储的FLS值
④调用FlsFree释放FLS索引
(2) FlsAlloc中可以指定一个回调函数:VOID WINAPI FlsCallback(PVOID lpFlsData);
回调函数会在纤程被销毁、调度纤程的线程退出或FlsFree时被调用,这主要便纤程有机会删除自己在FLS中存储的值。
(3) 获取当前纤程对象(上下文环境):PVOID GetCurrentFiber();
(4) 获得创建纤程时的pvParam数据:GetFiberData
(5) 判断是否正在某个纤程中运行:IsThreadFiber()
Windows长期以来给这个FLS Slot(槽位)设定了一个限制,一个进程最多有128个Slot。
从Windows 10 (build 18312) 版本开始,微软将这个限制大幅度提升到了4096个!
示例
#include <windows.h> #include <iostream> #include <ctime> // 回调函数,当纤程退出时调用 void CALLBACK FlsCallback(PVOID lpFlsData) { std::cout << "FlsCallback called with data: " << lpFlsData << std::endl; // 释放纤程上FLS中分配的内存 // 为NewFiber时,lpFlsData中存放的是一个int类型,数值为42的内容 // 为MainFiber时,lpFlsData中存放的是一个int类型,数值为56的内容 if (lpFlsData) { delete static_cast<int*>(lpFlsData); } } struct FiberParam { DWORD flsIndex; void* mainFiber; time_t createTime; }; void WINAPI NewFiberFunction(void* lpParameter) { // 获取 FLS 索引 FiberParam parameter = *static_cast<FiberParam*>(lpParameter); // 分配一些数据并存储在NewFiber的FLS中 int* pNewFiberData = new int(42); FlsSetValue(parameter.flsIndex, pNewFiberData); // 当前代码在NewFiber中执行,此时会将数据存放到NewFiber的FLS中 // 获取并打印存储在NewFiber的FLS中的数据 int* pNewFiberRetrievedData = static_cast<int*>(FlsGetValue(parameter.flsIndex)); if (pNewFiberRetrievedData) { std::cout << "NewFiber: FLS data = " << *pNewFiberRetrievedData << std::endl; } // 切换回主纤程 SwitchToFiber(parameter.mainFiber); } int main() { // 分配一个fls索引并设置回调函数 DWORD flsIndex = FlsAlloc(FlsCallback); if (flsIndex == FLS_OUT_OF_INDEXES) { std::cerr << "FlsAlloc failed" << std::endl; return 1; } // 将主线程转换为纤程 void* mainFiber = ConvertThreadToFiber(nullptr); if (!mainFiber) { std::cerr << "ConvertThreadToFiber failed" << std::endl; return 1; } // 创建一个新的纤程 FiberParam param; param.flsIndex = flsIndex; param.mainFiber = mainFiber; param.createTime = time(NULL); void* newFiber = CreateFiber(0, NewFiberFunction, ¶m); if (!newFiber) { std::cerr << "CreateFiber failed" << std::endl; return 1; } // 分配一些数据并存储在主线程Fiber的FLS中 int* pMainFiberData = new int(56); FlsSetValue(flsIndex, pMainFiberData); // 当前代码在主线程Fiber中执行,此时会将数据存放到主线程Fiber的FLS中 注:即使前面不执行CovertThreadToFiber也会存在一个主线程Fiber // 获取并打印存储在MainFiber的FLS中的数据 int* pMainFiberRetrievedData = static_cast<int*>(FlsGetValue(flsIndex)); if (pMainFiberRetrievedData) { std::cout << "MainFiber: FLS data = " << *pMainFiberRetrievedData << std::endl; } // 切换到新纤程 SwitchToFiber(newFiber); // 删除纤程 会触发FlsCallback回调函数 DeleteFiber(newFiber); // 不能在当前纤程逻辑中删除当前纤程 //DeleteFiber(mainFiber); // 释放FLS索引 此时所有存放数据在该FLS索引上且未删除Fiber,会逐一触发FlsCallback回调函数 FlsFree(flsIndex); return 0; }
参考
Announcing Windows 10 Insider Preview Build 18312