スレッド処理の中断について


フォレスタ  2005-08-16 04:10:14  No: 58662

度々お世話になります。

CWinThreadクラスのSuspendThread関数でスレッドを中断した場合、
ResumeThread関数で再開できるかと思いますが、
そのままスレッドを終了したい場合どのようにすればよいのでしょうか。
AfxEndThread関数だと、スレッド内部から呼び出さないといけない
ようなので、スレッド外部からスレッドを終了する等の方法があれば
と思っています。

なお、プログラムの内容は、ダイアログ表示と同時にスレッドを作成し
そのスレッドで長い処理が実行されます。
処理実行中に、ダイアログに配置されたボタンを押下することで処理を
中断したいと思っています。(下記にコードを示します。)

ちなみに、m_pThread_Main = NULL;
で、スレッド処理は終了するのでしょうか?

//ダイアログ表示時処理
BOOL CDlg::OnInitDialog() 
{
  CDialog::OnInitDialog();
  m_pThread_Main = AfxBeginThread( ThreadFunc_Main , this );  
                                                      //スレッド生成
  return TRUE;
}

UINT CDlg::ThreadFunc_Main( LPVOID pParam )
{
  ((CDlg*)pParam)->Main();  //メイン処理(長い処理)

  return 0;  //スレッド終了
}

void CDlg::Main()
{
  ...
  長い処理
  ...
}

void CDlg::OnButtonStop() 
{
  m_pThread_Main->SuspendThread();  //スレッドの中断
  
  int ret = AfxMessageBox("中止しますか?",MB_YESNO);

  if(ret == IDYES)  //中止する場合
  {
    //ここでスレッド(m_pThread_Main)を終了したい
  }
  else
  {
    m_pThread_Main->ResumeThread();  //処理続行
  }
}


てつや  2005-08-16 05:38:13  No: 58663

スレッドが何を行っているのかわからないのに終了させてしまっていいのですか?
例えばデータをコピーしている最中とかファイルをオープンして書き込みして
いる最中とか。

BOOL型の変数を1つ用意してFALSEで初期化、中止したい時にTRUEにします。
スレッド内の区切りがいい所でこの変数を確認し、TRUEになっていれば
自分で終了させる、という方が良いと思います。


エートリーぶ  2005-08-16 05:50:03  No: 58664

>処理実行中に、ダイアログに配置されたボタンを押下することで処理を
>中断したいと思っています。(下記にコードを示します。)
GUIから中断する手段をユーザーに提供するために、ワーカスレッドを
作成します。まさに、ワーカスレッドを作成する目的です。

>ちなみに、m_pThread_Main = NULL;
>で、スレッド処理は終了するのでしょうか?
↑思いっきり不味いコードです。

一応、外部からは  Win32でも  MFCでも  TerminateThread  が
使えそうですが、これを使うときは、他に手段が無いときです。
何故なら、メモリリークがおこります。

クラス(モジュール)の中に  static  的なフラグ変数を用意して
void CDlg::OnButtonStop() で変更し、その変更の監視は
void CDlg::Main()  で  ときどき  すれば良いです。
変更されていれば  return  か  AfxEndThread  でスレッドの
処理を内部から中断して  スレッドを抜けます。

#差し支えなければ、立ち入ったことを聞きますが  教えて下さい。
#提示されるプログラムの内容とフォレスタさんの知識にかなりの
#開きがあります。手直し仕事でしょうか?


YuO  2005-08-16 06:43:35  No: 58665

個人的には,イベントをワーカースレッド側がイベントをポーリングするようにして,
UIスレッドではイベントをセットするようにするのがお奨めです。

BOOL (or bool)でまかなう場合は,volatileをつけておいた方がよいでしょう。
# 最適化対策(たとえ現在のものが固定とみなさないとしても)。


エートリーぶ  2005-08-16 13:56:29  No: 58666

私を含めて3名とも内容的に同一の終了方法を提案しています。
私が説明したフラグ変数については、YuOさんが、ご指摘した  
volatile  にすると、より確実です。

//ここでスレッド(m_pThread_Main)を終了したい
↑で、このまま終了する場合は、TerminateThread  しかありませんが
通常の終了では、理由は既に示しているように  不味いです。
#エラー時の処理として、コードしては用意することはあります。

で、コードを拝見すると、SuspendThread  でスレッドを休止させて
いますので・・・。
#全部文章で思いましたが、やや苦手だからコード的に  (^^
#漢字コードが入るかも知れないので、コピペすると
#コンパイルエラーがでる可能性大

volatile BOOL flg=FALSE;  ←クラスの中に大域変数として用意

[1]
void CDlg::OnButtonStop() 
{
  m_pThread_Main->SuspendThread();  //スレッドの中断
  
  int ret = AfxMessageBox("中止しますか?",MB_YESNO);

  if(ret == IDYES)    //中止する場合
  {
    //ここでスレッド(m_pThread_Main)を終了したい
    //そうであれば、ここで  フラグを変更する
  flg=TRUE;
    
  }
  
    m_pThread_Main->ResumeThread();  //処理続行
  
}

[2]
スレッドは  ResumeThread();  で処理続行していて、なおかつ
ループで回していると思いますので(?)、ループの中でフラグが
変更されていれば、return  でスレッドを抜ける。変更されて
いなければ、「長い処理」の続行
↓こんな感じ
for(;;;)  {
  if(TRUE==flg)
    return;  
  ...
  長い処理
  ...

}

#もしかして、ループがない?・・・  そんときは
#「長い処理」の中に、if(TRUE==flg)  return;  を
#適量ばらまいて下さい。(この3行、普通は冗談です)


エートリーぶ  2005-08-16 14:28:16  No: 58667

送ってから気づきましたが、↓の2行のコードは必要なのですか?
SuspendThread();  //スレッドの中断
ResumeThread();  //処理続行
  
マルチスレッドだから、なくともGUIは反応するけど・・・。


フォレスタ  2005-08-16 19:16:00  No: 58668

回答ありがとうございます。

>スレッドが何を行っているのかわからないのに終了させてしまっていいのですか?
>例えばデータをコピーしている最中とかファイルをオープンして書き込みして
>いる最中とか。

スレッド中断後、ダイアログを閉じているのですが、
PostNcDestroy関数のなかで、ファイルを開いていたら閉じる処理
を行っているので大丈夫です。

>クラス(モジュール)の中に  static  的なフラグ変数を用意して
>void CDlg::OnButtonStop() で変更し、その変更の監視は
>void CDlg::Main()  で  ときどき  すれば良いです。
>変更されていれば  return  か  AfxEndThread  でスレッドの
>処理を内部から中断して  スレッドを抜けます。
この方法で行おうとしたのですが、
正直main関数の作りが悪く、非常に長いコードとなっていて、
フラグを確認しなければいけないタイミングを考慮して入れる
のが大変な状況にあります。
エートリーぶさんが想定されたループ処理ではないのです。

>#もしかして、ループがない?・・・  そんときは
>#「長い処理」の中に、if(TRUE==flg)  return;  を
>#適量ばらまいて下さい。(この3行、普通は冗談です)
最悪このようにしないといけないものでしょうか?


PATIO  2005-08-16 19:54:05  No: 58669

>>#もしかして、ループがない?・・・  そんときは
>>#「長い処理」の中に、if(TRUE==flg)  return;  を
>>#適量ばらまいて下さい。(この3行、普通は冗談です)
>最悪このようにしないといけないものでしょうか?

一方通行のループ無しのプログラムならそうなると思います。
これはコードの組み立て方の問題なので致し方ないでしょう。
うまくモジュール化して処理の繰り返しで動作が実現できるのであれば、
ループが出来るはずなので入れる場所もある程度限定は出来るかもしれませんけれど。

基本的にスレッドは自滅が基本だと思います。
ですから、スレッドを使う場合は、外部からスレッドを自滅させる為の仕組みを
あらかじめ考えておいて実装しておく必要があるでしょう。
スレッドの実行順序は同期オブジェクトを使って同期を取らない限り
不定と考えることが必要だと思います。


エートリーぶ  2005-08-16 20:03:05  No: 58670

>最悪このようにしないといけないものでしょうか?
ひょうたんから駒でしたか...orz

>正直main関数の作りが悪く、非常に長いコードとなっていて、
↑はスレッドの中のメインの処理と解釈して宜しいですね?

#PATIOさんと重複部分もありますが

対策1
完動させたものを、期日までにという条件が付随するのであれば
非常手段「TerminateThread」を使う。必ず、効果があるか確かめて
下さい。私はWin32からしか使った記憶がありません。

対策2
例の適量ばらまくコードを、慎重に区切りのよいところに挿入。

対策3
スレッドのメインの処理を適切に分割して、関数化して対策2
のコードを関数の区切りで入れる。対策2とほとんど同じ作業に
なりますが、後々のメンテではかなり楽になると思います。

#ループ化できるところはないのですか?


てつや  2005-08-16 20:31:19  No: 58671

>フラグを確認しなければいけないタイミングを考慮して入れる
>のが大変な状況にあります。

プログレスバーをStepItしているのでしょうから、そこを検索して、
StepItと終了確認及び終了処理をする関数に置き換えるとか。


フォレスタ  2005-08-17 03:24:28  No: 58672

回答ありがとうございます。

>基本的にスレッドは自滅が基本だと思います。
ということで、確実にスレッド内部から終了させることで
対応してみます。

>BOOL (or bool)でまかなう場合は,volatileをつけておいた方がよいでしょう。
># 最適化対策(たとえ現在のものが固定とみなさないとしても)。
修飾子volatileをつける理由が理解できていません。
最適化対策についてもう少し詳しくご教授願います。


YuO  2005-08-17 04:18:36  No: 58673

> 修飾子volatileをつける理由が理解できていません。

たとえば
bool b; // メンバ変数 or Global変数

// 関数中
b = true;
while (b);
というコードは,コンパイラによって,
b = true;
while (true);
と扱われるかもしれません。

volatileオブジェクトへのアクセスは抽象計算機の動作に忠実である必要があるために,
上記のようなことはできず,コード通りにコンパイルされます。


フォレスタ  2005-08-17 06:14:00  No: 58674

回答ありがとうございます。

volatile修飾子を付ける必要性、コンパイル時の最適化について
よくわかりました。ありがとうございます。


フォレスタ  2005-08-17 19:12:45  No: 58675

>一応、外部からは  Win32でも  MFCでも  TerminateThread  が
>使えそうですが、これを使うときは、他に手段が無いときです。
>何故なら、メモリリークがおこります。

中断状態のスレッドを外部から
メモリリークしないようにするために
  delete m_pThread_Main
するのはまずいでしょうか?

確実にスレッド内部から終了させることで
対応しようとしたのですが、
スレッド処理内に多くのエラー時のメッセージ表示処理があり、
中止ボタンを押した後に、メッセージが表示されてしまう
恐れがある(実際は表示されず処理が固まってしまう)
ので、対応が困難となっています。


isshi  2005-08-17 21:53:05  No: 58676

>中断状態のスレッドを外部から
>メモリリークしないようにするために
>  delete m_pThread_Main
>するのはまずいでしょうか?
まずいです。

>対応が困難となっています。
困難だろうがちゃんと対応すべきです。


フォレスタ  2005-08-17 23:04:45  No: 58677

回答ありがとうございます。

>>中断状態のスレッドを外部から
>>メモリリークしないようにするために
>>  delete m_pThread_Main
>>するのはまずいでしょうか?
>まずいです。

理由をご教授願います。


YuO  2005-08-17 23:31:28  No: 58678

>>中断状態のスレッドを外部から
>>メモリリークしないようにするために
>>  delete m_pThread_Main
>>するのはまずいでしょうか?
>まずいです。

というか,意味がなさそうですが。
単に,スレッドハンドルを閉じるだけの意味しかなく,
スレッドは終了しないからです。

・OSレベルでのスレッドそのもの (スレッドIDはこっちに付属)
・スレッドを操作するためのハンドル
という二つのうち,CWinThreadはあくまで後者をラッピングしたものです。

ちなみに,Win32でハンドルを使うものは,大抵こういう構造になっています。
オブジェクトへのハンドルによる参照がなくなった時点でオブジェクトは破棄されるのですが,
スレッドやプロセスなどは,実行中である限りオブジェクトは破棄されません。
# というか,破棄されたらたまったものではない。


PATIO  2005-08-18 02:20:38  No: 58679

基本的にスレッドは自滅しないといけないと言うのは、
早い話が外部から安全に強制終了させる方法が存在しないからです。
スレッド内の後始末はスレッド内でしか出来ませんが、
TerminateThreadは、スレッド内の後処理を実行することなく、
単にスレッドを終わらせてしまいます。
後処理にあたる部分が実行されなければ、そこで開放するはずのリソースが
リークするのは自明の理でしょう。

既に書かれているようにCWinThreadはあくまでもスレッドの管理をやりやすく
する為の物であり、スレッドそのものではありません。
スレッド内で確保されたリソースはスレッドの処理で責任を持って開放しなくてはだめなのです。

>スレッド処理内に多くのエラー時のメッセージ表示処理があり、
>中止ボタンを押した後に、メッセージが表示されてしまう
>恐れがある(実際は表示されず処理が固まってしまう)
>ので、対応が困難となっています。
きちんと外部から終了させられていると言う事をスレッドに認識させて
終了中のエラーメッセージの出力を抑止するとか、ログに吐き出すように
切り替えるとかするべきですね。
スレッドを安全に終わらせる為の代替手段は他にはありません。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加