25

次の例は、Ubuntu12.04でClang3.2またはGCC4.7を使用してコンパイルすると正常に実行されます(つまり、ハングしません)が、VS11BetaまたはVS2012RCを使用してコンパイルするとハングします。

#include <iostream>
#include <string>
#include <thread>
#include "boost/thread/thread.hpp"

void SleepFor(int ms) {
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

template<typename T>
class ThreadTest {
 public:
  ThreadTest() : thread_([] { SleepFor(10); }) {}
  ~ThreadTest() {
    std::cout << "About to join\t" << id() << '\n';
    thread_.join();
    std::cout << "Joined\t\t" << id() << '\n';
  }
 private:
  std::string id() const { return typeid(decltype(thread_)).name(); }
  T thread_;
};

int main() {
  static ThreadTest<std::thread> std_test;
  static ThreadTest<boost::thread> boost_test;
//  SleepFor(100);
}

問題std::thread::join()は、終了後に呼び出された場合に戻らないということのようmainです。cthread.cWaitForSingleObjectで定義されているでブロックされます。_Thrd_join

SleepFor(100);の最後でコメントを外すと、非静的mainにするのと同様に、プログラムを適切に終了できます。std_testを使用boost::threadすると、問題も回避できます。

それで、ここで未定義の動作を呼び出しているのか(私にはありそうもないようです)、またはVS2012に対してバグを報告する必要があるのか​​どうかを知りたいですか?

4

5 に答える 5

26

VS2012 RTMを使用した接続バグ( https://connect.microsoft.com/VisualStudio/feedback/details/747145 )のFraserのサンプルコードをトレースすると、デッドロックのかなり単純なケースが示されているようです。これはおそらく特定のものではありませんstd::thread-おそらく_beginthreadex同じ運命に苦しんでいます。

デバッガーに表示されるのは次のとおりです。

メインスレッドでは、main()関数が完了し、プロセスクリーンアップコードが_EXIT_LOCK1のデストラクタと呼ばれるクリティカルセクションを取得ThreadTestし、2番目のスレッドで(への呼び出しを介して)終了するのを(無期限に)待機していますjoin()

2番目のスレッドの無名関数が完了し、スレッドクリーンアップコード内で_EXIT_LOCK1クリティカルセクションの取得を待機しています。残念ながら、物事のタイミング(2番目のスレッドの無名関数の存続期間が関数の存続期間を超えるmain())により、メインスレッドはすでにそのクリティカルセクションを所有しています。

デッドロック。

メインスレッドがデッドロック状態を回避する前にmain()2番目のスレッドが取得できるように、の存続期間を延長するもの。_EXIT_LOCK1そのため、スリープのコメントを解除するmain()と、クリーンなシャットダウンが発生します。

または、ローカル変数からstaticキーワードを削除するThreadTestと、デストラクタ呼び出しはmain()関数の最後まで移動され(プロセスクリーンアップコードではなく)、2番目のスレッドが終了するまでブロックされます-デッドロック状態を回避します。

または、ThreadTestその呼び出しjoin()に関数を追加し、最後にその関数を呼び出すことができますmain()。これもデッドロック状態を回避します。

于 2012-11-22T07:37:15.143 に答える
6

これはVS2012に関する古い質問だと思いますが、バグはVS2013にまだ存在しています。おそらくマイクロソフトがVS2015のアップグレード価格を提供することを拒否したために、VS2013に固執している人のために、私は以下の分析と回避策を提供します。

問題は、正確な状況に応じて、によって使用されるミューテックス(at_thread_exit_mutex_Cnd_do_broadcast_at_thread_exit()がまだ初期化されていないか、すでに破棄されていることです。前者の場合、_Cnd_do_broadcast_at_thread_exit()シャットダウン中にミューテックスを初期化しようとすると、デッドロックが発生します。後者の場合、ミューテックスがすでにatexitスタックを介して破棄されているため、プログラムは途中でクラッシュします。

_Cnd_do_broadcast_at_thread_exit()私が見つけた解決策は、プログラムの起動の早い段階で明示的に呼び出すことです(ありがたいことに公に宣言されています)。これには、他の誰かがミューテックスにアクセスしようとする前にミューテックスを作成する効果があり、ミューテックスが最後の可能な瞬間まで存在し続けることを保証します。

したがって、問題を修正するには、ソースモジュールの下部、たとえばmain()の下に次のコードを挿入します。

#pragma warning(disable:4073) // initializers put in library initialization area
#pragma init_seg(lib)

#if _MSC_VER < 1900
struct VS2013_threading_fix
{
    VS2013_threading_fix()
    {
        _Cnd_do_broadcast_at_thread_exit();
    }
} threading_fix;
#endif
于 2015-12-10T16:46:26.383 に答える
1

私はあなたのスレッドがすでに終了していて、あなたのメイン機能の終了後、静的な破壊の前にそれらのリソースが解放されていると信じています。これは、少なくともVC6にまでさかのぼるVCランタイムの動作です。

親スレッドが終了したときに子スレッドは終了しますか

Windowsでスレッドとプロセスのクリーンアップを強化する

于 2012-06-06T13:38:49.590 に答える
0

私の答えは遅すぎますが、希望は誰かを助けるでしょう。

私はこのバグに悩まされていました、そして私はこの問題を解決するためのトリックを見つけました、それは私のコードで働きました。

int main()
{
    ThreadTest trick_obj;  //trick... You can put this line of code anywhere
    static ThreadTest std_test;
    return 1;
}
于 2019-11-09T08:40:34.463 に答える
-1

私はこのバグと1日戦い続けており、次の回避策を見つけました。これは、最も汚いトリックではないことが判明しました。

戻る代わりに、標準のWindows API関数呼び出しExitThread()を使用してスレッドを終了できます。もちろん、このメソッドはstd :: threadオブジェクトと関連するライブラリの内部状態を台無しにする可能性がありますが、プログラムはとにかく終了するので、そうです。

#include <windows.h>

template<typename T>
class ThreadTest {
 public:
  ThreadTest() : thread_([] { SleepFor(10); ExitThread(NULL); }) {}
  ~ThreadTest() {
    std::cout << "About to join\t" << id() << '\n';
    thread_.join();
    std::cout << "Joined\t\t" << id() << '\n';
  }
 private:
  std::string id() const { return typeid(decltype(thread_)).name(); }
  T thread_;
};

join()呼び出しは明らかに正しく機能します。ただし、私はソリューションでより安全な方法を使用することを選択しました。std :: thread :: native_handle()を介してスレッドHANDLEを取得できます。このハンドルを使用すると、WindowsAPIを直接呼び出してスレッドに参加できます。

WaitForSingleObject(thread_.native_handle(), INFINITE);
CloseHandle(thread_.native_handle());

その後、デストラクタがスレッドに2回参加しようとするため、std::threadオブジェクトを破棄しないでください。したがって、プログラムの終了時にstd::threadオブジェクトをぶら下げたままにしておきます。

于 2013-11-03T22:17:54.517 に答える