2

現在、数学的分析で構成されるデスクトップアプリケーションに取り組んでいます。GUIとc++で記述されたプロジェクトにqtを使用しています。ユーザーが分析を開始すると、ワーカースレッドを開いてプログレスバーを開始します。これまではすべて問題ありませんでしたが、ユーザーが操作をキャンセルすると問題が発生します。操作が複雑で、複数の関数とオブジェクトを使用しています。いくつかのメモリを割り当て/割り当て解除します。キャンセル操作で回復するにはどうすればよいかを知りたいメモリリークが発生する可能性があるため、キャンセル操作を堅牢かつ安全に行うには、どのパターンまたは方法を使用する必要がありますか?

私の考えは例外をスローすることですが、操作は非常に複雑なので、すべての関数にtry-catchを適用する必要がありますか、それともより一般的な方法であるパターンがあります。

編集:問題は、オブジェクトがスコープ間で転送されるため、shared_ptrまたはauto_ptrで問題が解決しないことです。フラグのアイデアは解決できますが、非常に多くのコードが必要であり、簡単な方法があるはずです。

4

8 に答える 8

9

ワーカー スレッドを閉じる非常に一般的な方法は、フラグを付けて、ワーカー スレッドにこのフラグを定期的に検査させることです。マークされている場合は、ワークフローを中止し、クリーンアップして終了する必要があります。

あなたの状況でその可能性はありますか?

于 2009-01-08T21:18:41.543 に答える
4

ワーカー スレッドは、停止するメッセージを確認する必要があります。メッセージは、フラグまたはイベントを介して送信できます。停止メッセージを受信したら、スレッドを終了する必要があります。

割り当てられたすべてのメモリに BOOST セーフ ポインターを使用します。終了時にメモリリークはありません。これまで。

于 2009-01-08T21:31:56.103 に答える
2

割り当てられたメモリが所有されていることを確認してください

割り当てられたすべてのメモリが、C ++ 03のauto_ptr、C ++ 11のunique_ptr、Boostのscoped_ptr、またはshared_ptr(共有、コピー、移動が可能)のいずれかのスマートポインタによって所有されていることを確認してください。

このようにして、RAIIはメモリリークからユーザーを保護します。

Boost.Thread1.37を使用する

スレッドを中断するためのさまざまな方法を説明しているハーブサッターの記事、InterruptPolitelyを読んでください。

今日、 Boost.Thread 1.37を使用すると、例外をスローしてスレッドを終了するように要求できます。Boostでは、これはboost :: thread_interrupted例外であり、任意の割り込みポイントから例外をスローします。

したがって、ある種のメッセージループを処理したり、グローバル/共有データを検証したりする必要はありません。メインスレッドはワーカースレッドに例外を介して停止するように要求し、ワーカースレッドが割り込みポイントに到達するとすぐに例外がスローされます。前述のRAIIメカニズムにより、割り当てられたすべてのデータが正しく解放されます。

スレッドで呼び出される疑似コードがあるとしましょう。これは、おそらくメモリを割り当てる関数のようなものであり、ループ内で多くの計算を行う関数のようなものである可能性があります。

Object * allocateSomeObject()
{
   Object * o = NULL ;

   if(/*something*/)
   {
      // Etc.
      o = new Object() ;
      // Etc.
   }

   return o ; // o can be NULL
}

void doSomethingLengthy()
{
   for(int i = 0; i < 1000; ++i)
   {
      // Etc.
      for(int j = 0; j < 1000; ++j)
      {
         // Etc.
         // transfert of ownership
         Object * o = allocateSomeObject() ;
         // Etc.
         delete o ;
      }
      // Etc.
   }
}

上記のコードは安全ではなく、メモリがC ++オブジェクト(通常はスマートポインタ)によって所有されることを常に確認するための手順が実行されない場合、割り込みモードに関係なくリークします。

このように変更して、コードを割り込み可能にし、メモリセーフにすることができます。

boost::shared_ptr<Object> allocateSomeObject()
{
   boost::shared_ptr<Object> o ;

   if(/*something*/)
   {
      // Etc.
      boost::this_thread::interruption_point() ;
      // Etc.
      o = new Object() ;
      // Etc.
      boost::this_thread::interruption_point() ;
      // Etc.
   }

   return o ; // o can be "NULL"
}

void doSomethingLengthy()
{
   for(int i = 0; i < 1000; ++i)
   {
      // Etc.
      for(int j = 0; j < 1000; ++j)
      {
         // Etc.
         // transfert of ownership
         boost::shared_ptr<Object> o = allocateSomeObject() ;
         // Etc.
         boost::this_thread::interruption_point() ;
         // Etc.
      }

      // Etc.
      boost::this_thread::interruption_point() ;
      // Etc.
   }
}

void mainThread(boost::thread & worker_thread)
{
   // etc.
   if(/* some condition */)
   {
      worker_thread.interrupt() ;
   }
}

Boostを使用しませんか?

Boostを使用しない場合は、これをシミュレートできます。スレッドを中断する必要がある場合は、スレッドストレージのブール値のような変数を「true」に設定します。この変数をチェックする関数を追加し、trueの場合は特定の例外をスローします。スレッドの「ルート」にこの例外をキャッチさせて、正しく終了させます。

免責事項

現在、Boost 1.37にアクセスできないため、前のコードをテストすることはできませんが、アイデアはあります。私はこれをできるだけ早くテストし、最終的にはより完全な/正しい/コンパイル可能なコードを投稿します。

于 2009-01-10T02:36:14.030 に答える
0

Qtを使用しているので、QObjectの子育てメモリシステムを利用できます。

ワーカースレッドの実行中にメモリの割り当てと割り当て解除を行うと言います。各割り当てがQObjectのインスタンスである場合、それを現在のQThreadオブジェクトの親にしないのはなぜですか?

MyObject * obj = new MyObject( QThread::currentThread() );

途中で削除することもできますが、それを見逃した場合は、QThreadの割り当てが解除されたときにクリーンアップされます。

ワーカーQThreadにキャンセルするように指示する場合は、QThreadインスタンスを削除する前に、QThreadが終了するのを待つ必要があることに注意してください。

workerThread->cancel();  // reqfuest that it stop
workerThread->wait();    // wait for it to complete
delete workerThread;     // deletes all child objects as well

私はあなたがあなたの記憶戦略としてあなたのスレッドとQObjectをやめるためにSimonJensenの答えを使用します、私はあなたが良い状況にあると思います。

于 2009-01-11T07:52:32.457 に答える
0

動的に割り当てられたリソースを自動 (スタック上に存在するローカル) セントリー オブジェクトに保持するようにしてください。これらのリソースは、スコープ外になったときにデストラクタで解放されます。このようにして、関数が例外のために終了したとしても、それらがリークしないことを知ることができます。また、ルーチン間でメモリを共有するためのブースト ライブラリ shared_ptr を調査することもできます。

于 2009-01-08T21:27:15.957 に答える
0

まず、例外をスローするマルチスレッド アプリケーションは、それらを処理するための標準的な方法がないため、不安定です (例外は他のスレッドに伝播しますか? スケジューラーですか? main()? どこか別の場所ですか?)。少なくとも、標準化されたスレッドが組み込まれた C++0x ライブラリを入手するまでは。

当面は、RAII を使用する方が理にかなっています (これにより、メモリを含むすべてのリソースが、スコープが成功または失敗によって存在するかどうかにかかわらず、スコープが終了したときにクリーンアップされることが保証されます)、ある種のステータス コードが返されます。最も理にかなっているスレッド (たとえば、スケジューラー) に。

また、スレッドを直接キャンセルすることは、10 年以上にわたって推奨されていません。Simon Jensen が示唆しているように、スレッドにそれ自体を停止させ、スレッドにクリーンアップを処理させる方がはるかに優れています。

于 2009-01-08T21:32:03.123 に答える
0

この問題に対する一般的な解決策はありません。

いくつかの可能な戦略:

  • shared_ptrs とフレンドの使用が役立つ場合があります
  • キャンセル機能がアルゴリズムを混乱させたくない場合は、スローすることを検討してください。トップレベル関数をキャッチし、そこからクリーンアップします。
  • ヒープではなくスタックに何を置いても、リークは発生しません
  • クラス間に多くのポインタがあるヒープ上の大きな構造の場合、通常、メモリ構造全体の割り当てを解除する方法を厳密に提供することが問題になります。
  • キャンセル時に破棄するメモリのプールに新しい配置を検討しましたか?

しかし、いずれにせよ、戦略を立てるか、痛みを感じるでしょう。

于 2009-01-08T21:32:40.737 に答える
0

答えは、操作の複雑さに依存するということです。

ここにはいくつかのアプローチがあります。1)前述のように、操作に「キャンセル」フラグを設定し、その操作に定期的な(近い)間隔で、おそらく少なくともプログレスバーを更新するのと同じくらい頻繁にキャンセルフラグをポーリングさせます。ユーザーがキャンセルを押したら、キャンセル ルーチンを押します。

さて、このシナリオでのメモリ処理に関しては、いくつかの方法で行いました。私の好みは、スコープ外に出ると自動的にクリーンアップされるスマート ポインターまたは STL オブジェクトを使用することです。基本的に、メモリのクリーンアップを処理するデストラクタを持つオブジェクト内でオブジェクトを宣言します。これらのオブジェクトを作成すると、メモリが作成され、オブジェクトが範囲外になると、メモリは自動的に削除されます。メモリを処理するために「dispose」メソッドのようなものを追加することもできます。次のようになります。

class MySmartPointer { 
     Object* MyObject;
     MySmartPointer() { MyObject = new Object(); }
     ~MySmartPointer() { if (MyObject != null) { delete MyObject; MyObject = null; }}
     void Dispose() { if (MyObject != null) { delete MyObject; MyObject = null; } }
     Object* Access() { return MyObject; }
 }

本当に賢くしたい場合は、そのクラスをテンプレート化して、任意のオブジェクトに対してジェネリックにしたり、配列などを持たせたりすることができます。もちろん、オブジェクトにアクセスする前にオブジェクトが破棄されているかどうかを確認する必要があるかもしれませんが、ポインターを直接使用すると壊れます。実行中に関数呼び出しのコストがかからないように、Access メソッドをインライン化することもできます。

2) goto メソッド。メモリを先頭に宣言し、最後に削除し、cancel メソッドを押したら goto を呼び出してメソッドの最後に移動します。goto は非常に悪いスタイルと見なされているため、一部のプログラマーはこれを理由にあなたをリンチする可能性があると思います。ループの方法として基本と 'goto 10' を学んだので、それほど怖くはありませんが、コード レビュー中に衒学者に答える必要があるかもしれないので、本当に良い説明が必要です。オプション1ではなく、これを選択した理由。

3)すべてをスレッドではなくプロセスに入れます。可能であれば、操作するすべての情報をディスクにシリアル化し、別のプログラムで複雑な分析を実行します。そのプログラムが停止したとしても、メイン アプリケーションが破壊されることはありません。分析が複雑で 32 ビット マシン上にある場合は、とにかく実行するためにそのすべてのメモリ空間が必要になる場合があります。共有メモリを使用して進行状況情報を渡すのではなく、進行状況をディスクに読み書きするだけで、キャンセルは即座に行われます。実装は少しトリッキーですが、不可能ではなく、はるかに安定している可能性があります。

于 2009-01-08T21:33:14.963 に答える