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

时间:2012-08-13 11:15:28

标签: c++ windows visual-c++

TL; DR:在这种情况下,我猜测DLL加载程序锁定死锁是否正确,我怎么能确定?

我的某些代码中存在间歇性死锁(50%),涉及CRT时间函数和National Instruments DAQmx驱动程序(9.3.5f2)。我正在使用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 Instruments\MAX\mxs.dllNational Instruments\MAX\mxsutils.dll和{{1} })。

在MSVC 2008运行时SysWOW64\version.dll映射到localtime,显然它在Windows下使用线程本地存储以便线程安全。

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

主线程:

Scanned 10 critical sections

第二个帖子:

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:\david\dev\nitests\deadlock2\deadlock2.cpp @ 60]
0035f7c8 7509339a 7efde000 0035f814 77289ef2 deadlock2!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.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

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

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

在这种情况下,我有办法验证导致死锁的原因吗?

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:1)

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

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