什么可能导致时间函数和 DLL 加载之间死锁



TL;DR:在这种情况下,我对DLL加载程序锁死锁的猜测是否正确,我如何确定?

我在某些涉及CRT时间函数和NIDAQmx驱动程序(9.3.5f2)的代码中出现间歇性死锁(50%)。我正在使用MSVC2008 Express创建一个x86可执行文件(典型的"发布"设置,如果需要可以提供),并且我在Win7 Pro x64上运行。我的代码使用主线程上的时间函数并启动一个新线程来处理更新模拟输出电压(在 USB-6009 上):

#include <iostream>
#include <ctime>
#include <windows.h>
#include <process.h>
#include <NIDAQmx.h>
HANDLE  g_TerminateEvent;
extern "C" unsigned int WINAPI DacUpdateThreadRunner(void *lpParam)
{
    TaskHandle  taskHandle;
    DAQmxCreateTask("", &taskHandle);
    DAQmxCreateAOVoltageChan(taskHandle, "Dev2/ao0", "", 0.0, 3.3, DAQmx_Val_Volts, "");
    DAQmxStartTask(taskHandle);
    float64 sample_value = 0.0;
    bool    quit = false;
    while (!quit)
    {
        DWORD wait_result = WaitForSingleObject(g_TerminateEvent, 32);
        if (wait_result == WAIT_OBJECT_0) quit = true;
        else
        {
            DAQmxWriteAnalogScalarF64(taskHandle, 1, 1.0, sample_value, NULL);
        }
    }
    DAQmxStopTask(taskHandle);
    DAQmxClearTask(taskHandle);
    return 0;
}
int main(void)
{
    g_TerminateEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    unsigned int m_ThreadId;
    uintptr_t m_Thread = _beginthreadex(NULL, 0, DacUpdateThreadRunner, NULL, 0, &m_ThreadId);
    struct tm t;
    time_t tt = time(NULL);
    struct tm *temp = localtime(&tt);
    memcpy(&t, temp, sizeof(struct tm));
    for (int i = 0; i < 10; i++)
    {
        std::cout << "Main thread doing stuff " << i << std::endl;
        Sleep(1000);
    }
    SetEvent(g_TerminateEvent);
    CloseHandle((HANDLE)m_Thread);
    return 0;
}

如果我调用代码localtime(),它似乎只会死锁。查看MSVS中的调试输出,当第二个线程加载(许多)NI DLL(死锁之前要加载的最后一个DLL是National InstrumentsMAXmxs.dllNational InstrumentsMAXmxsutils.dllSysWOW64version.dll)时,它似乎锁定了。

在 MSVC 2008 运行时中,localtime映射到localtime64(),显然它在 Windows 下使用线程本地存储以实现线程安全。

在应用程序死锁后,我使用 WinDbg 获取调用堆栈(如下所示)并使用 !locks 命令,但我不明白为什么会出现死锁,因为我看不到两个线程都锁定的任何共享资源。locks 命令输出Scanned 10 critical sections但没有其他内容(我需要使用 Windows 的已检验版本吗?

主线程:

ChildEBP RetAddr  Args to Child              
0035f078 77288df4 000001d0 00000000 00000000 ntdll_77250000!NtWaitForSingleObject+0x15
0035f0dc 77288cd8 00000000 00000000 00000000 ntdll_77250000!RtlpWaitOnCriticalSection+0x13e
0035f104 772a9520 773520c0 773271ca 0035f350 ntdll_77250000!RtlEnterCriticalSection+0x150
0035f144 751a1ee1 005e0000 0035f35c bf6b8258 ntdll_77250000!LdrGetDllHandleByMapping+0x3b
0035f304 751a1fd2 0035f350 0035f348 00000002 KERNELBASE!BasepLoadLibraryAsDataFileInternal+0x4f4
0035f324 751a2221 0035f350 0035f348 00000002 KERNELBASE!BasepLoadLibraryAsDataFile+0x19
0035f360 751993ad 0035f38c 00000000 006a7eb4 KERNELBASE!LoadLibraryExW+0x18a
0035f598 75199535 0035f630 72cbc018 00000002 KERNELBASE!ConvertTimeZoneMuiString+0xe4
0035f5bc 7519966b 0035f5d8 72cbbfc4 72cbc018 KERNELBASE!ConvertTimeZoneMuiStrings+0x155
0035f688 75199729 72cbbfc0 00000001 0035f6f0 KERNELBASE!GetTimeZoneInformationRaw+0x8c
0035f698 72c58d90 72cbbfc0 bf60abfe 0035f778 KERNELBASE!GetTimeZoneInformation+0xf
0035f6f0 72c59390 bf60aa2e 0035f778 00f625f8 MSVCR90!_set_timezone+0x168
0035f720 72c59e79 01103384 00f625f8 000001cc MSVCR90!__tzset+0x2e
0035f748 72c5a0b1 00f625f8 0035f778 00000001 MSVCR90!_localtime64_s+0x9f
0035f75c 01101107 0035f778 01103384 00000001 MSVCR90!_localtime64+0x1a
0035f784 011015e9 00000001 00f61850 00f62ba8 deadlock2!main+0x57 [c:daviddevnitestsdeadlock2deadlock2.cpp @ 60]
0035f7c8 7509339a 7efde000 0035f814 77289ef2 deadlock2!__tmainCRTStartup+0x10f [f:ddvctoolscrt_bldself_x86crtsrccrtexe.c @ 586]
0035f7d4 77289ef2 7efde000 7732789a 00000000 KERNEL32!BaseThreadInitThunk+0xe
0035f814 77289ec5 01101731 7efde000 00000000 ntdll_77250000!__RtlUserThreadStart+0x70
0035f82c 00000000 01101731 7efde000 00000000 ntdll_77250000!_RtlUserThreadStart+0x1b

第二个线程:

ChildEBP RetAddr  Args to Child              
0276e618 77288df4 0000021c 00000000 00000000 ntdll_77250000!NtWaitForSingleObject+0x15
0276e67c 77288cd8 00000000 00000000 72c83b4e ntdll_77250000!RtlpWaitOnCriticalSection+0x13e
0276e6a4 72c42f2a 72cbbab8 0276e978 0276e6ec ntdll_77250000!RtlEnterCriticalSection+0x150
0276e6b4 72c48a70 00000007 bd23bbe2 00f69170 MSVCR90!_lock+0x30
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for mxsutils.dll - 
0276e6ec 1b4fc523 0276e700 00000105 0276e978 MSVCR90!_getcwd+0x13
WARNING: Stack unwind information not available. Following frames may be wrong.
0276e80c 1b4fd45c 0276e81c 0276e9a4 1b5009e7 mxsutils!mxsCheckComponent+0x67c3
0276e838 1b515a05 0276e9a4 0276e978 00000000 mxsutils!mxsCheckComponent+0x76fc
0276e994 1b51542a 0276e9a4 00f619c0 0276e9b0 mxsutils!std::_Init_locks::operator=+0x1a4f
0276e9d8 1b502aa0 0276eaf0 00f69170 00f619c0 mxsutils!std::_Init_locks::operator=+0x1474
0276eb54 1b4f163f 00000001 00000001 006b0300 mxsutils!CodeProject3rdParty::mxs_mxExceptionFilter+0x320
0276eba8 1b502831 1b240000 1b529f40 00000001 mxsutils+0x163f
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for mxs.dll - 
0276ebc8 1b2414e9 00000001 00000000 00f62540 mxsutils!CodeProject3rdParty::mxs_mxExceptionFilter+0xb1
0276edf8 1b24593c 1b240000 00000001 00000000 mxs+0x14e9
0276ee3c 1b2459f6 1b240000 0276ee68 77289950 mxs!std::_Init_locks::operator=+0x44c
0276ee48 77289950 1b240000 00000001 00000000 mxs!std::_Init_locks::operator=+0x506
0276ee68 7728d8c9 1b2459d8 1b240000 00000001 ntdll_77250000!LdrpCallInitRoutine+0x14
0276ef5c 7728d78c 00000000 75717046 00000000 ntdll_77250000!LdrpRunInitializeRoutines+0x26f
0276f0c8 7728c4d5 0276f12c 0276f0f4 00000000 ntdll_77250000!LdrpLoadDll+0x4d1
0276f100 751a2288 0276f0f4 0276f144 0276f12c ntdll_77250000!LdrLoadDll+0xaa
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for nidmxfu.dll - 
0276f13c 6dd8b3ad 00000000 00000000 006b03fc KERNELBASE!LoadLibraryExW+0x1f1
0276f568 6dd8b4d3 0276f86c 6ded4a38 0276f92c nidmxfu!nNIMSAI100::tFilterPreferences::~tFilterPreferences+0x65cd
0276f584 6dc39d62 0276f6cc 0276f728 0276f870 nidmxfu!nNIMSAI100::tFilterPreferences::~tFilterPreferences+0x66f3
00000000 00000000 00000000 00000000 00000000 nidmxfu!nNIMS100::tAttributeDatabase::getAttributeValueForString+0x12432

我的猜测是,主线程在 MSVCRT 中锁定了一个内部锁,然后加载了一个 DLL,它无法加载,因为线程 2 有一个 DLL 加载器锁。线程 2 尝试使用 MSVCRT 中的 getcwd(),然后导致死锁。这是一个准确的评估吗?如果没有,我该如何获取更多信息以确保?

如果我确信这是问题所在,我可以通过重新排序一些代码(例如,使用 wxDateTime 或主线程中的 NI 代码来预加载 DLL)来解决它。但是,我不想只是隐藏它,让它重新出现并稍后咬我。

那么,在这种情况下,我有没有办法验证是什么导致了僵局?

您的诊断是正确的。 tzset在呼叫LoadLibrary时保持锁。与此同时,_getcwd正在等待同样的锁。 mxsutils正在从其DllMain内部呼叫_getcwd。与大多数函数一样,从DllMain调用_getcwd是不安全的。临时解决方法是在创建任何线程之前从mainlocaltime进行虚拟调用。一个长期的解决方法是更改msxutils,这样它就不会从内部调用不安全的函数DllMain

我注意到你在调用wxDateTime::Now()时没有对wxWidgets系统进行任何初始化。 我的猜测是wxDateTime::Now()依赖于在执行普通wxWidgets初始化时初始化的内容。 您是否尝试过甚至不启动其他线程,而只是检查wxDateTime::Now()是否像这样工作正常?

我还注意到您正在使用wxWidgets v2.9.2。建议您升级到 v2.9.4。除了许多可能有助于您的情况的改进之外,这些是对 wxDateTime 中错误的修复。可能无法帮助解决当前问题,但会解决您还不知道遇到的问题

最新更新