0

C++ クラス内にスレッドを埋め込む必要がありますが、アクティブ オブジェクトの一種ですが、厳密にはそうではありません。私はクラスのコンストラクターからスレッドを生成しています。この方法で問題はありますか?

#include <iostream>
#include <thread>
#include <unistd.h>

class xfer
{
        int i;
        std::shared_ptr<std::thread> thr;
        struct runnable {
                friend class xfer;
                void operator()(xfer *x) {
                        std::cerr<<"thread started, xfer.i:"<<x->i;
                }
        } run;
        public:
        xfer() try : i(100), thr(new std::thread(std::bind(run, this))) { } catch(...) { }
        ~xfer() { thr->join(); }
};

int
main(int ac, char **av)
{
        xfer x1;
        return 0;
}
4

3 に答える 3

4

一般に、スレッドで使用されるオブジェクトが開始前に完全に構​​築されていれば、コンストラクターでスレッドを開始しても問題はありません 。(したがって、たとえば、コンストラクターでそれ自体でスレッドを開始するスレッド基本クラスを持つというイディオムは壊れています。)あなたの場合、スレッドオブジェクトがあなたの memberrunを使用するため、この基準を満たしていません。スレッドを開始するまで構築されません。スレッドの作成をコンストラクターの本体に移動するか、クラス定義内のデータ メンバーの順序を変更するだけで、これが修正されます。(後者を行う場合は順序が重要である旨とその理由についてコメントを追加してください。)

デストラクタの呼び出しjoinはより問題があります。不確定な時間待機する可能性のある操作は、IMHO、デストラクタで問題があります。スタックの巻き戻し中にデストラクタが呼び出された場合、他のスレッドが終了するのを待つのは望ましくありません。

また、おそらくクラスをコピー不可にしたいと思うでしょう。(その場合は必要ありませんshared_ptr。) コピーする joinと、同じスレッドで 2 回実行することになります。

于 2012-06-14T12:24:20.380 に答える
2

コードに競合状態があり、安全ではありません。

問題は、メンバー変数の初期化がクラスで宣言されている順序で発生することです。つまり、メンバーthrが member の前に初期化されますrun。の初期化中に onthrを呼び出すと、(まだ初期化されていない)オブジェクトが内部的にコピーされます (そこにコピーしていることをご存知ですか?)。std::bindrunrun

runポインターを渡した(またはコピーを避けるために使用std::refした)と仮定してみましょうstd::bind。コードにはまだ競合状態があります。アクセスされない限り、まだ初期化されていないメンバーにポインター/参照を渡すstd::bindことは問題ないため、この場合、ポインター/参照を渡すことは問題になりません。ただし、オブジェクトがスレッドを生成する速度が速すぎる (たとえば、コンストラクターを実行しているスレッドが削除され、新しいスレッドが処理される)という競合状態が依然として存在し、オブジェクトが処理されるにスレッドが実行されてしまう可能性があります。初期化されました。std::threadrun.operator() run

コードを安全にするために、型のフィールドを並べ替える必要があります。メンバーの順序を少し変更すると、コードの有効性に悪影響を与える可能性があることがわかったので、より安全な設計を検討することもできます。(この時点でそれは正しく、実際に必要なものかもしれませんが、後でフィールドを並べ替えないように、少なくともクラス定義にコメントを付けてください)

于 2012-06-14T12:14:53.483 に答える
0

コンストラクタでの使用は安全かもしれませんが...

  • 関数呼び出しでベア を使用することは避けるべきですnew
  • thr->join()デストラクタでの使用は...問題を示しています

詳細に...

new関数呼び出しでベアを使用しないでください

オペランドの実行順序は指定されていないため、例外セーフではありません。ここで使用することを好むstd::make_sharedか、の場合はunique_ptr、独自の機能を作成しますmake_unique(完全な転送と可変個のテンプレートを使用すると、機能します)。これらのビルダー メソッドにより、RAII クラスへの所有権の割り当てと帰属が確実に分割されずに実行されます。

3つのルールを知っている

3 つのルールは経験則 (C++03 で確立) であり、コピー コンストラクター、代入演算子、またはデストラクターを記述する必要がある場合は、おそらく他の 2 つも記述する必要があるということです。

あなたの場合、生成された代入演算子は正しくありません。つまり、2 つのオブジェクトを作成したとします。

 int main() {
     xfer first;  // launches T1
     xfer second; // launches T2

     first = second;
 }

次に、割り当てを実行すると、への参照が失わT1れますが、参加したことはありません!!!

呼び出しが必須かどうかは思い出せjoinませんが、クラスに一貫性がありません。必須でない場合は、デストラクタを削除してください。必須の場合は、1 つの共有スレッドのみを処理する下位レベルのクラスを作成し、共有スレッドの破棄の前に常にjoin呼び出しが行われるようにし、最後にその共有スレッド機能を直接使用する必要があります。xferクラス。

経験則として、要素のサブセットに対して特別なコピー/割り当てのニーズがあるクラスは、おそらく分割して、特別なニーズを持つ要素を 1 つ (または複数) の専用クラスに分離する必要があります。

于 2012-06-14T11:53:11.857 に答える