VS2012のスレッド関連C++ライブラリのバグ回避方法メモ
VS2012のC++ライブラリに含まれるスレッド関係がボロボロ
いまさらだけどVS2012で追加されているC++11関連のスレッド関係ライブラリがひどい。
もう使っているケースはあまりないと思いますが、注意喚起と対策をメモ。
1. std::threadのメモリリーク
C++11規格から追加されたstd::threadクラスは、それまで起動にプラットフォーム固有の関数を使う必要があったスレッドをC++クラスとしてラッピングし標準ライブラリに組み込まれたもの.
長らく待望されていた非常に便利なクラスなのですが、VisualStudio2012付属の標準ライブラリのthread実装は、デストラクト時に内部のリソースが解放されないメモリリークの障害があり.
memory leak for std::thread
これはworkaroundがない。。
std::threadを回避するしかない。。
2. std::asyncの引数付きオーバーロードがコンパイルエラーになる
std::asyncには2種類のテンプレートオーバーロードがある.
コレもひどい話。
【Workaround】
asyncの引数で関数に渡すパラメータを与えず、一つ目のオーバーロードを使用する.
具体的には、関数を引数を含めたラムダ式で与えるか、ラムダ式内で直接値を参照すれば回避ができる.
3. std::futureが正しい状態を返さない.
並行処理の結果データの受け渡しを簡単に記述できるようにした promise/future なのですが、受信側のクラスfutureに致命的ともいえるバグがある.
std::asyncやstd::packaged_taskによって返される、std::futureオブジェクトが、future::get(並行処理が完了し結果を取得)もしくはfuture::wait(無期限待機)するまで正しい状態を返さない問題.
つまり終了してるのかどうか、待機するまでわからない。
これは本気で困った.
StackOverflow記事「std::future still deferred when using std::packaged_task(VS11)」
C++11 unexpected behavior for std::future::wait_for and std::packaged_task
「VS2013で治ってるからそっち使ってね♪」って・・
↑これ、フザけんな!(笑
一瞬殺意がわきましたよ。。
海外のサイトでもみんな回避策を模索したようだけど、みんなお手上げ。
まぁ、普通の方法ではお手上げなんだよね。
なので、チョイワルなことをしてみようとライブラリのコードを追っていくと、asyncでdeffer指定するようなケース以外はうまく動作させることが出来そうなハックを思いつく。
ホント、ソースが見られるOpenSourceってありがたいね。
【Workaround】
ということで、コンパイラを騙してVS2012のfutureを使えるようにするWorkaroundを実装してみよう。
以下の手順.
ということでサンプル.
まずは1を.
つづいて2
定義したクラスを使ってfutureの内部情報を取り出しreturn値を差し替え.
futureの本来の仕様通りの動作をする代替実装を行う.
validと同様にwait_forなども実装すれば、まぁ、何とか使える状態にできた。
いまさらだけどVS2012で追加されているC++11関連のスレッド関係ライブラリがひどい。
もう使っているケースはあまりないと思いますが、注意喚起と対策をメモ。
1. std::threadのメモリリーク
C++11規格から追加されたstd::threadクラスは、それまで起動にプラットフォーム固有の関数を使う必要があったスレッドをC++クラスとしてラッピングし標準ライブラリに組み込まれたもの.
長らく待望されていた非常に便利なクラスなのですが、VisualStudio2012付属の標準ライブラリのthread実装は、デストラクト時に内部のリソースが解放されないメモリリークの障害があり.
memory leak for std::thread
これはworkaroundがない。。
std::threadを回避するしかない。。
2. std::asyncの引数付きオーバーロードがコンパイルエラーになる
std::asyncには2種類のテンプレートオーバーロードがある.
- std::asyncの引数に並行処理する関数\/関数オブジェクトのみを受け取るもの
- std::asyncの引数に並行処理する関数\/関数オブジェクトと、関数に渡すパラメータを受け取るもの
コレもひどい話。
【Workaround】
asyncの引数で関数に渡すパラメータを与えず、一つ目のオーバーロードを使用する.
具体的には、関数を引数を含めたラムダ式で与えるか、ラムダ式内で直接値を参照すれば回避ができる.
std::future
f = std::async(std::launch::async, foo, 5, 7);
↓
std::futuref = std::async(std::launch::async, []{ foo(5,7); });
3. std::futureが正しい状態を返さない.
並行処理の結果データの受け渡しを簡単に記述できるようにした promise/future なのですが、受信側のクラスfutureに致命的ともいえるバグがある.
std::asyncやstd::packaged_taskによって返される、std::futureオブジェクトが、future::get(並行処理が完了し結果を取得)もしくはfuture::wait(無期限待機)するまで正しい状態を返さない問題.
つまり終了してるのかどうか、待機するまでわからない。
これは本気で困った.
StackOverflow記事「std::future still deferred when using std::packaged_task(VS11)」
C++11 unexpected behavior for std::future::wait_for and std::packaged_task
「VS2013で治ってるからそっち使ってね♪」って・・
↑これ、フザけんな!(笑
一瞬殺意がわきましたよ。。
海外のサイトでもみんな回避策を模索したようだけど、みんなお手上げ。
まぁ、普通の方法ではお手上げなんだよね。
なので、チョイワルなことをしてみようとライブラリのコードを追っていくと、asyncでdeffer指定するようなケース以外はうまく動作させることが出来そうなハックを思いつく。
ホント、ソースが見られるOpenSourceってありがたいね。
【Workaround】
ということで、コンパイラを騙してVS2012のfutureを使えるようにするWorkaroundを実装してみよう。
以下の手順.
- Mixin-from-belowイディオムを使用して、protectedメンバを読み出したいクラスの派生クラス(hackerクラス)を定義. → future_hacker, state_hacker
- protectedメンバを参照したいオブジェクトの参照 を それぞれ hackerクラスのポインタにシレッとキャスト.
※ これは教科書的にNGなはずのキャストだけど、C++の規格上オブジェクトのメモリ配置が全く同じなので、問題なく動作しちゃう裏ワザ. OSSでもごくたまに見かけますね.
ということでサンプル.
まずは1を.
#include < chrono > // std::chrono #include < future > // std::future // VS2013からはちゃんと動くので、あくまでVS2012専用. #if (_MSC_VER < 1800) && (_MSC_VER >= 1700) /*! * @brief VS2012のfuture関連バグ対処用名前空間 * * VS2012専用です * VS2012は標準ライブラリにバグがあり、futureのメソッドが正しい値を返さない.
* 内部には正しい情報が収まっているようなので、 * VS2012版futureのhackerクラスを使用してオブジェクト内部から情報を取り出す. */ namespace stlhack { // ----------------------------------------------------------------- /*! * @brief 内部状態管理オブジェクトの内部情報にアクセスするためのHackerクラス */ template < typename Ty > class state_hacker : public std::_Associated_state< Ty > { public: // ------------------------------------------------------------- /*! * @brief futureに結果が格納されているか判定する. * * protectedメンバ _Has_stored_result を読み出して返すpublic関数. * * @return 結果を保持しているかどうか */ bool HasStoredResult() const { return (this->_Has_stored_result); } }; // ------------------------------------------------------------- /*! * @brief futureクラスの内部情報を取り出すためのHackerクラス * * @tparam futureT future もしくは shared_futre * @tparam Ty futureTの型パラメータ */ template < template < typename > class futureT, typename Ty > class future_hacker : public futureT < Ty > { public: //! 基底クラス型 // VS2012だとusing句が使えなーいのでtypedefで我慢. typedef typename futureT < Ty > base_t; // ------------------------------------------ /*! * @brief オブジェクトの有効性を返す * @retval true オブジェクトは有効 * @retval false オブジェクトは無効 */ bool valid() const _NOEXCEPT { bool ret(false); auto assoc_state = static_cast< state_hacker< Ty > *>(_Ptr()); // ← 裏技. if (assoc_state) { bool retrived = assoc_state->_Already_retrieved(); bool has_result = assoc_state->HasStoredResult(); ret = static_cast < bool > (has_result && !retrived); } return ret; } // -------------------------------------------- /*! * @brief 指定時間待機して結果の状態を返す * * @tparam Rep tickを表す数値型 * @tparam Per a std::ratio. 1tickの間の時間. * @param [in] Rel_time 待機時間 * @return std::future_status 型 */ template < class Rep, class Per > std::future_status::future_status wait_for(const std::chrono::duration < Rep, Per >& Rel_time) const { // wait自体は本来のfutureの機能を使用する. // ただし、Bugのため戻値に誤っている値が返るので、正しい値で上書きしてやる. // _Ptr()は、std::_Associated_state < Ty > のポインタを返す. auto ret = base_t::wait_for(Rel_time); auto assoc_state = static_cast < state_hacker< Ty >* > (_Ptr()); // ← 裏技. if (assoc_state) { // std::_Associated_state< Ty >のpublicメソッド _Is_ready を使用して // 内部の状態を取得. ret = (assoc_state->_Is_ready()) ? std::future_status::ready : std::future_status::timeout; } return ret; } }; } #endif
つづいて2
定義したクラスを使ってfutureの内部情報を取り出しreturn値を差し替え.
futureの本来の仕様通りの動作をする代替実装を行う.
/*! * @brief futureが値を取得できる状態か判定. * * VS2012のfutureバグ対処済み. * * @param [in] fu futureオブジェクト(std::future < T > もしくは std::shared_futre < T > のオブジェクト) * @retval true 取得できる値を保持している * @retval false 取得できる値を保持していない. */ template < template < typename > class futureT, typename T > auto future_valid(const futureT < T > & fu) -> bool { #if (_MSC_VER < 1800) && (_MSC_VER >= 1700) // * VS2012専用. * using namespace stlhack; auto hacker = static_cast< const future_hacker< futureT, T >* >(&fu); // ← 裏技. auto ret = hacker->valid(); return ret; #else // VS2013以降もしくはMac用 // 本来のあるべき処理はこちら auto ret = fu.valid(); return ret; #endif } /*! * @brief 指定時間待機. * * VS2012のfutureバグ対処済み. * * @tparam Rep tickを表す数値型 * @tparam Per a std::ratio. 1tickの間の時間. * @param [in] fu futureオブジェクト(std::future < T > もしくは std::shared_futre < T > のオブジェクト) * @param [in] Rel_time 待機時間 * @return std::future_status 型 */ template < template < typename > class futureT, typename T, typename Rep, typename Per > auto future_wait_for( const futureT < T > & fu, const std::chrono::duration < Rep, Per > & Rel_time ) -> decltype(fu.wait_for(Rel_time)) { #if (_MSC_VER < 1800) && (_MSC_VER >= 1700) // * VS2012専用. * using namespace stlhack; auto hacker = static_cast< const future_hacker < futureT, T >* >(&fu); // ← 裏技. auto ret = hacker->wait_for(Rel_time); return ret; #else // VS2013以降もしくはMac用 // 本来のあるべき処理はこちら auto ret = fu.wait_for(Rel_time); return ret; #endif }
validと同様にwait_forなども実装すれば、まぁ、何とか使える状態にできた。