9

私は C++ 開発者で、最近まで Windows 向けのアプリケーションの作成を余儀なくされるまで、主に Solaris と Linux でプログラミングを行っていました。

私は、TCP ソケットに支えられた C++ I/O ストリームに基づく通信設計を使用してきました。この設計は、他のスレッドが同じストリームを介して送信する (mutex によって同期される) 間、ストリームから継続的に読み取る単一のスレッド (ほとんどの場合、データを待機するソケット読み取りでブロックされる) に基づいています。

Windows に移行するとき、boost::asio::ip::tcp::iostream を使用してソケット ストリームを実装することにしました。上記のマルチスレッド設計により、Windows でデッドロックが発生したことを知り、がっかりしました。はoperator<<(std::basic_ostream<...>,std::basic_string<...>)、入力操作と出力操作の両方でストリーム全体をロックする「Sentry」を宣言しているようです。読み取りスレッドは常にストリームを待機しているため、この Sentry が作成されると、他のスレッドからの送信操作がデッドロックします。

operator<< と Sentry の構築中のコール スタックの関連部分を次に示します。

    ...
    ntdll.dll!7c901046()    
    CAF.exe!_Mtxlock(_RTL_CRITICAL_SECTION * _Mtx=0x00397ad0)  Line 45  C
    CAF.exe!std::_Mutex::_Lock()  Line 24 + 0xb bytes   C++
    CAF.exe!std::basic_streambuf<char,std::char_traits<char> >::_Lock()  Line 174   C++
    CAF.exe!std::basic_ostream<char,std::char_traits<char> >::_Sentry_base::_Sentry_base(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...})  Line 78   C++
    CAF.exe!std::basic_ostream<char,std::char_traits<char> >::sentry::sentry(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...})  Line 95 + 0x4e bytes  C++
>   CAF.exe!std::operator<<<char,std::char_traits<char>,std::allocator<char> >(std::basic_ostream<char,std::char_traits<char> > & _Ostr={...}, const std::basic_string<char,std::char_traits<char>,std::allocator<char> > & _Str="###")  Line 549 + 0xc bytes   C++
    ...

istream コンポーネントと ostream コンポーネントが別々にロックされていれば問題ありませんが、そうではありません。

使用できるストリーム演算子の代替実装はありますか? ロックしないように指示できますか?自分で実装する必要がありますか (これを行う方法がわからない)。

任意の提案をいただければ幸いです。

(プラットフォームは Windows 32 ビットおよび 64 ビットです。Visual Studio 2003 Pro および 2008 Express で観察された動作)

4

5 に答える 5

1

この質問は十分に長い間衰退してきました。揶揄される可能性はありますが、私がやったことを報告します。

問題は、個別の読み取り操作と書き込み操作で iostream オブジェクトにアクセスしようとしているときに、2 つのスレッドがデッドロックに陥っていることであると、私はすでに判断していました。文字列ストリームの挿入演算子と抽出演算子の両方の Visual Studio 実装が、操作対象のストリームに関連付けられたストリーム バッファーをロックする Sentry を宣言していることがわかります。

このデッドロックで問題となっているストリームでは、ストリーム バッファの実装が boost::asio::basic_socket_streambuf であることはわかっていました。実装を調べて、読み取り操作と書き込み操作 (アンダーフローとオーバーフロー) が実際には異なるバッファー (get と put) で動作することを確認しました。

上記を確認して、このアプリケーションのロックを単純に回避することにしました。そのために、プロジェクト固有のプリプロセッサ定義を使用して、ロッキング セントリーの basic_istream 実装でロッキング コードを除外しました。

    class _Sentry_base
        {   // stores thread lock and reference to input stream
    public:
        __CLR_OR_THIS_CALL _Sentry_base(_Myt& _Istr)
            : _Myistr(_Istr)
            {   // lock the stream buffer, if there
#ifndef MY_PROJECT
            if (_Myistr.rdbuf() != 0)
                _Myistr.rdbuf()->_Lock();
#endif
            }

        __CLR_OR_THIS_CALL ~_Sentry_base()
            {   // destroy after unlocking
#ifndef MY_PROJECT
            if (_Myistr.rdbuf() != 0)
                _Myistr.rdbuf()->_Unlock();
#endif
            }

利点:

  • できます
  • 自分のプロジェクト (適切な定義を含む) のみが影響を受けます

欠点:

  • 少しハッキーな感じ
  • これが構築される各プラットフォームには、この変更が必要です

これをコードとプロジェクトのドキュメントで大声で文書化することにより、後者の点を軽減する予定です。

これにはもっと洗練された解決策があるかもしれませんが、便宜上、影響を理解するために十分な注意を払った後、直接的な解決策を選択しました。

于 2009-07-30T23:57:40.793 に答える
1

ブーストのドキュメント [1] によると、ミューテックスなしで 1 つのオブジェクトにアクセスする 2 つのスレッドの使用は「安全ではありません」。Unix プラットフォームで動作したからといって、Windows プラットフォームで動作するという保証はありません。

したがって、オプションは次のとおりです。

  1. スレッドがオブジェクトに同時にアクセスしないようにコードを書き直してください
  2. ブースト ライブラリにパッチを適用し、変更を送り返します
  3. Chris に、Windows プラットフォームの変更を行うかどうかをうまく尋ねてください。

[1] http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/overview/core/threads.html

于 2009-07-24T05:30:52.800 に答える
0

おそらく、ロック層を自分で実装できますか? IE、別のistreamandostreamがあり、それらが呼び出されたときに自分でロックします。定期的に両方のロックが解除されているかどうかを確認し、一方から他方に読み取ります。

于 2009-07-16T23:13:38.070 に答える
0

書き込み後にストリームを明示的にフラッシュしましたか? このブログ投稿は、データが単にバッファ内で「スタック」している可能性があることを暗示しています。それが本当なら、まだ何も読み取れないため、デッドロックしているように見えるかもしれません。stream << std::flush送信操作の最後に追加します。

ブログ投稿で提案されている代替の (効率は劣りますが) 解決策は、ストリームの出力バッファリングをオフにすることです。

stream.rdbuf()->pubsetbuf(0, 0);
于 2009-07-23T03:20:22.383 に答える
0

私はこれが古い質問であることを知っています...しかし、私はこれを自分でしなければなりませんでした!

これは私自身のストリームバッファであるため、私の状況はより複雑でしたが、次のようにして修正できます。

std::ostream &operator<<(std::ostream &x, std::string &y)
{
  x.rdbuf()->_Unlock();
  x << y.c_str();
}

std:: バージョンに優先して呼び出されます。

もちろん、_Lock を呼び出すすべての Windows operator<< と、(私の場合は) streambuf 内のすべての読み取り/書き込み呼び出しに対してこれを行う必要があります。

于 2012-12-19T18:10:27.823 に答える