6

つまり、これが私が話していることです: std は複雑です。

VS2013 では、この単純なプログラムでデッドロックが発生します。

#include <thread>
#include <windows.h>

void foo()
{
}

void initialize()
{
    std::thread t(foo);
}

BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID)
{
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        initialize();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

DLLMain でスレッドを作成するのは完全に間違っていますか? それは真実ではない。Microsoft のドキュメント「DLL を作成するためのベスト プラクティス」から: 「スレッドの作成は、他のスレッドと同期しない場合に機能する可能性があります」。したがって、CreateThread は機能し、_beginthreadex は機能し、boost::thread は機能しますが、std::thread は機能しません。これはコール スタックです。

ntdll.dll!_NtWaitForSingleObject@12()
KernelBase.dll!_WaitForSingleObjectEx@12()
msvcr120d.dll!Concurrency::details::ExternalContextBase::Block() Line 151
msvcr120d.dll!Concurrency::Context::Block() Line 63
msvcr120d.dll!Concurrency::details::_Condition_variable::wait(Concurrency::critical_section & _Lck) Line 595
msvcp120d.dll!do_wait(_Cnd_internal_imp_t * * cond, _Mtx_internal_imp_t * * mtx, const xtime * target) Line 54
msvcp120d.dll!_Cnd_wait(_Cnd_internal_imp_t * * cond, _Mtx_internal_imp_t * * mtx) Line 81
msvcp120d.dll!std::_Cnd_waitX(_Cnd_internal_imp_t * * _Cnd, _Mtx_internal_imp_t * * _Mtx) Line 93
msvcp120d.dll!std::_Pad::_Launch(_Thrd_imp_t * _Thr) Line 73
mod.dll!std::_Launch<std::_Bind<1,void,void (__cdecl*const)(void)> >(_Thrd_imp_t * _Thr, std::_Bind<1,void,void (__cdecl*const)(void)> && _Tg) Line 206
mod.dll!std::thread::thread<void (__cdecl&)(void)>(void (void) * _Fx) Line 49
mod.dll!initialize() Line 17
mod.dll!DllMain(HINSTANCE__ * __formal, unsigned long reason, void * __formal) Line 33

さて、 std::thread は「他のスレッドと同期」します。

しかし、なぜ ?

これがVS2015で二度と起こらないことを願っています。まだテストしていません。

4

4 に答える 4

8

の仕様にstd::threadは、次の要件が含まれています (N4527 §30.3.1.2[thread.thread.constr]/6)。

同期: コンストラクターの呼び出しの完了は、のコピーの呼び出しの開始と同期しますf

(ここfで、新しく作成されたスレッドで実行される呼び出し可能なエンティティです。)

のコンストラクターはstd::thread、新しいスレッドがスレッド プロシージャの実行を開始するまで戻ることができません。新しいスレッドが作成されると、スレッド プロシージャが呼び出される前に、読み込まれた各 DLL のエントリ ポイントが呼び出されDLL_THREAD_ATTACHます。これを行うには、新しいスレッドがローダー ロックを取得する必要があります。残念ながら、既存のスレッドはすでにローダー ロックを保持しています。

したがって、デッドロックが発生します。既存のスレッドは、新しいスレッドがスレッド プロシージャの実行を開始するまでローダー ロックを解放できませんが、新しいスレッドは、既存のスレッドによって保持されているローダー ロックを取得できるまで、スレッド プロシージャを実行できません。

ドキュメントでは、DLL エントリ ポイントからスレッドを作成しないことを明示的に推奨していることに注意してください。

内から次のタスクを実行しないでくださいDllMain: [...] を呼び出しCreateThreadます。スレッドの作成は、他のスレッドと同期しなければ機能しますが、危険です。

(そのページには、DLL エントリ ポイントから実行してはならないことの長いリストがあります。これはその 1 つにすぎません。)

于 2015-08-28T14:02:34.770 に答える
0

メンバー関数を使用detach()してクラッシュを修正します。例:

void Hook_Init();

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        {
            std::thread hookthread(Hook_Init);
            hookthread.detach();
            break;
        }
    }
    return TRUE;
}

void Hook_Init()
{
    // Code
}
于 2016-05-15T11:19:01.053 に答える
0

プラットフォーム レベルとstdレベルを混在させています。生の winapi 関数CreateThreadを呼び出すと、DllMain. std::threadしかし、 がプラットフォームとどのように相互作用するかについての保証はありません。このようなことを で行うことは非常に危険であることはよく知られているDllMainので、まったくお勧めしません。std試してみることを主張する場合は、実装の結果を回避するために、つま先立ちして winapi を直接呼び出す必要があります。

「理由」については、それほど重要ではありませんが、デバッガーをざっと見てみると、MSVC 実装には、引数とリソースを交換するための新しいスレッドとのハンドシェイクがあるようです。そのため、リソースがいつ渡されたかを知るには同期が必要です。合理的なようです。

于 2015-08-27T14:41:50.350 に答える