ま、そんなところで。

ニッチな技術系メモとか、車輪を再発明してみたりとか.

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種類のテンプレートオーバーロードがある.
  • std::asyncの引数に並行処理する関数\/関数オブジェクトのみを受け取るもの
  • std::asyncの引数に並行処理する関数\/関数オブジェクトと、関数に渡すパラメータを受け取るもの
上記のうち後者がコンパイルエラーになる.
コレもひどい話。

【Workaround】
asyncの引数で関数に渡すパラメータを与えず、一つ目のオーバーロードを使用する.
具体的には、関数を引数を含めたラムダ式で与えるか、ラムダ式内で直接値を参照すれば回避ができる.

std::future f = std::async(std::launch::async, foo, 5, 7);
      ↓
std::future f = 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を実装してみよう。
以下の手順.

  1. Mixin-from-belowイディオムを使用して、protectedメンバを読み出したいクラスの派生クラス(hackerクラス)を定義. → future_hacker, state_hacker
  2. 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なども実装すれば、まぁ、何とか使える状態にできた。