スレッドの戻り値を取得できるのでしょうか

[戻る]
なおぞう 2016/02/05(金) 20:06:05
VS2013 VC++ Win32スタティックライブラリ プロジェクト
MFC利用なし、Unicode文字型 な環境で開発しています。

スレッドにコールバック関数のポインタを渡して中で処理をするという動作をし、内部で
起きたエラー値を取得したい(できたら戻り値で)のです。

typedef DWORD(*CallbackFunc)(DWORD);


void c1(DWORD aaa)
{
  printf("%d\n",aaa);
  return;
}

main()
{
CallbackFunc tp;
tp = c1;

DWORD ThreadID
HANDLE  hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)test,
(LPVOID)tp, NULL, &ThreadID);

}

上記のようにコールバック関数のポインタを引数にしたスレッドを作成して、下記のよう
なスレッド関数を書きました。

DWORD  WINAPI test(CallbackFunc p)
{
  DWORD res = GetStatus();
    DWORD res2 = GetTestName();
   
    p(res);//コールバック関数に結果を返す
    return res2;

}

スレッド関数の書き方はネットで探して書いたものになりますが、返り値となっている
DWORDの行く末がどうなってしまうのかが分かりません。
スレッド関数の戻り値を取得する事は可能なのでしょうか?
スレッド関数の処理中に呼び元関数は終わってしまい、戻り値なんて受け取れないのに、
なぜ、スレッド関数の戻り値をvoidでなくてDWORDにしているサンプルばかりなのでしょ
うか。戻り値をDWORDにしている意味を教えていただけると助かります。(WINAPIを付け
る理由もわからないです。なくても動くようですし)

また、戻り値が取れる方法があればそれも合わせて教えていただけますようお願いします
←こちらのほうが重要です。呼び出した関数の実行結果が欲しいので。

よろしくお願いします。

tetrapod 2016/02/06(土) 08:14:03
GetExitCodeThread
https://msdn.microsoft.com/ja-jp/library/cc429118.aspx
ThreadProc
https://msdn.microsoft.com/ja-jp/library/cc429382.aspx

> 戻り値をDWORDにしている意味を教えていただけると助かります。
> WINAPIを付ける理由もわからないです。
Microsoft がそういう仕様にしているから。
GetExitCodeThread も LPDWORD を指定する仕様だ。
ThreadProc には WINAPI と明記されている。

> なくても動くようですし
単なる偶然だ (x64 だと必然)

なんだけど CreateThread を直接使うことはまったくもって推奨されない。
オイラ、マルチスレッドプログラムは何度も書いてるけど一度も使ったことないよ。
MFC を使っているなら AfxBeginThread
C ランタイムライブラリを使っているなら _beginthreadex

AR 2016/02/06(土) 11:17:48
>戻り値をDWORDにしている意味を教えていただけると助かります。

 意識しないで省略されているっぽいですが
int main()
↑これと、ほぼ同義かと思います。

 基本的にスレッドは自殺するように作るので、私も戻り値を使ったことはないです。
 強いて言えばデバッグウィンドウに、スレッドの戻り値が○○で終了しましたと表示され
るので、あーあのスレッドが終了したんだなと見分けるくらいでしょうか。
 簡単に情報をやり取りするなら、volatile宣言を付けたグローバル変数に戻り値らしき
ものを入れておけばスレッド間で情報のやり取りができます。

 ただ、おそらくスレッドの概念がちょっと違うんじゃないかなと思っています。
 例えば、「戻り値を返す」という動作は、その時点でスレッドが終了している or 終了
を待っている、なので並列に動作していることになりえません。
 同期処理に関してもうちょっと勉強すると、その過程でスレッド制御に関しても学べる
んじゃないかと思います。

ITO 2016/02/06(土) 13:19:21
うーーん、
HANDLE  hThreadがNULLのとき失敗したというぐらいかな?
ただし、コールバック関数の戻り値ではないです。

普通は、コールバック関数内で処理する変数の一つにそういったステータスの変数を
加えますね。

>WINAPIを付ける理由もわからないです。
CreateThreadは、コンパイラーが対応していれば、Cならなんでもコンパイルできます。
ただし、動作は保証できないです。
使用できる関数はtetrapodさんの意見通り、
>MFC を使っているなら AfxBeginThread
>C ランタイムライブラリを使っているなら _beginthreadex
ですね。

WINAPIは、そのままのとおり
  ウインドウアプリとして使用します。
というコンパイラーに対しての宣言です。

普通は、グローバルなbool型変数を作って、コールバック関数内でエラーになったら
falseにセットするぐらいかな?

ITO 2016/02/06(土) 13:21:35
修正です。
>使用できる関数
推奨されている関数
ですね

ITO 2016/02/06(土) 14:00:09
たびたびすまんです。

>普通は、グローバルなbool型変数を作って、コールバック関数内でエラーになったら
>falseにセットするぐらいかな?
これなしですね

仲澤@失業者 2016/02/06(土) 16:13:06
1.スレッドのコールバックを持つスレッドを生成するクラスを作る
2.このクラスはスレッドの戻り値を持つ
3.スレッド関数は渡されたポインタが生成者自身であることを知っている
4.スレッド関数は生成者のコールバックを呼ぶ
5.このクラスを使うものはスレッドクラスのポインタを開始時に受け取る
6.::WaitForSingleObject()でスレッドが終了するのを待つ
7.結果を受け取る。

ソースはこんな感じ(コンパイルしてないのでご注意)

----Thd.h----
class Thd{
  friend UINT __cdecl ThreadFunc( LPVOID pParam); 
  int  Result; // 実行結果
  void ThdCallBack(){
    :
    // スレッド実行
    :
    Result = 結果を代入
  }
public:
  int GetResult(){ // 結果を戻す
    return Result;
  }
 CWinThread * ThreadStart(){
    retrun = ::AfxBeginThread( ThreadFunc, this));
  }
}
----Thd.cpp----
UINT __cdecl ThreadFunc( LPVOID pParam)
{
   Thd * Owner = (Thd *)pParam;
   pParam->ThdCallBack();
   return 0;
}
----メインなど----
class MainApp
{
  Thd   m_Thd;
  foo(){
    CWinThread * thread = m_Thd.ThreadStart();
    ::WaitForSingleObject( thread, INFINITE);
    int Res = m_Thd.GetResult(); // 結果を取得
  }
}
いろいろ問題はあると思いますが、こんな感じでいけるかも。

なおぞう 2016/02/08(月) 10:58:48
>tetrapod様

>> WINAPIを付ける理由もわからないです。
>Microsoft がそういう仕様にしているから。

仕様なのですね。Microsoftのページで確認してみます。

>なんだけど CreateThread を直接使うことはまったくもって推奨されない。
>C ランタイムライブラリを使っているなら _beginthreadex

スレッドはとりあえずシングルスレッドな扱いで問題なさそうですが…CreateThreadって
推奨されていないのですか。ネットで探したらすぐに出来てたのがこれだったので使い始
めていました。
beginthreadexは、CreateThreadと違ってハンドルを閉じなくていいようですね。でも、
引数を構造体にしなくちゃいけない…とかなんだか使い方がややこしそうすが、この関数
に変更してみます。
推奨ってことだから特に変えなくても不具合はないのでしょうか。


>AR 様
main()関数、確かに戻り値intですね。自動生成されていて全く意識していませんでし
た。volatileも良く分からないですし、スレッドも今回初めて使うので、AR様の言うとお
り、もっと勉強する必要がありますね。(別スレッドで同じ変数を使う場合の対処方法と
かも考えなくてはいけないのでしょうけど、なかなかたどりつけないです)

>ITO様
エラー管理構造体を用意しておいて、スレッド関数内で起きたエラー情報をセットしてお
こうかと考えています。
また、スレッド関数が終わったっていう状態を呼び側に返すことも難しいようなので、と
りあえずスレッド関数が抜けるときにフラグをオフにするような操作を入れてみようと思
います。

>仲澤@失業者様
コードまで書いていただきありがとうございます。
ぱっと見ただけでは理解できてないので、後でじっくり読ませていただきたいと思います。


そういえば、今作っているのはDLLなので、 
typedef DWORD(*CallbackFunc)(DWORD); も

typedef DWORD(WINAPI *CallbackFunc)(DWORD);
にしないと駄目なようですね。
スレッドのサイトを見ていたらプライマリスレッドとか、また意味のわからない単語が出
てきました。いろいろと奥が深いです。

なおぞう 2016/02/08(月) 11:23:51
CreateThread で作ったスレッド内で ランタイムライブラリを使うとメモリリークする
のですね。現状、自分が関数中にランタイムライブラリを使っているか判断できないです
が…。

ITO 2016/02/08(月) 11:36:08
>CreateThread で作ったスレッド内で ランタイムライブラリを使うとメモリリーク
>するのですね。
CreateThreadに限らず、スレッド内のプログラムは必ず終了するようにしないと
メモリリークします。

なおぞう 2016/02/08(月) 12:22:10
>ITO様

スレッド内のプログラムは終了って
main()
{
HANDLE  hThead = (HANDLE)_beginthreadex(NULL, 0, testThread1,(Void*)"test", 0,
NULL);
    CloseHandle(hThead );
}

unsigned __stdcall testThread1(void *p)
{
    char * bun = (char*)p;
    printf(%s\n",bun);
    _endthreadex(0); ←ここですか?
      //CreateThradの場合は     ExitThread(0);でしょうか。 
      //現状作っているソースでは、スレッド内で終了するときは、ExitThread(0);を呼
んでました…間違っているのでしょうか。
    return 0;
}

今回見つけたサンプルは、スレッド関数の戻り値が unsignedになっていて、また混乱し
てきました。CreateThreadではWRODでしたし。
CreateThreadで作成すると、WORDがスレッド関数のも戻り値になって、_beginthreadexの
場合はunsignedになるのでしょうか。
しかも、私の乏しい知識では、unsignedは unsigned int というような使い方であり、
unsignedとだけの使い方は今回初めて見ました。混乱します。

とりあえず_beginthreadexの正しい作り方、終わらせ方など調べてみます。

tetrapod 2016/02/08(月) 13:23:42
1. unsigned とだけ書いたら unsigned int の省略と解釈される (C/C++ 言語仕様)

2. _beginthreadex でスレッドを起動する場合、スレッド関数は unsigned を返す仕様
CreateThread では、スレッド関数は DWORD を返す仕様
Visual C++ では DWORD は unsigned int なので結果的に同じもの
(DWORD は Windows 固有の型という扱い unsigned int は Windows 固有でない)

3.スレッドは、
正規の終了:スレッド起動関数から return することで正しく終了することができる。

スレッド起動関数から呼び出している更に内側の関数からスレッドを終了したいときは
_endthreadex を呼ぶことで「非正規な終了」をさせることができる
※なぜこの方法が非正規か
→自動変数のデストラクタが呼ばれないから

4.提示サンプルはせっかく得た hThread を GetExitCodeThread を呼ぶ目的で使う前に
CloseHandle してるけど意味ないぞ。

5.タイミング制御が重要なとき CREATE_SUSPENDED を使うと良い

6. CreateThread したスレッドの中で wprintf とか fopen とか使うと誤動作する。
そういう意味で _beginthreadex がよい

7. _beginthread/_endthread は使い道が無いので ex 版を強く推奨する

というわけで

unsigned __stdcall threadentryfunc(void* argp) {
    int arg=*reinterpret_cast<int*>(argp);
    wprintf(L"arg=%d\n*, arg);
    return 711; // スレッド起動関数が return すると内部で _endthreadex される
}

int wmain() {
    int arg=876;
    unsigned threadID; // スレッド ID とは タスクマネージャ等で見える値のこと
    unsigned exitcode;
    // スレッドハンドルとは、そのスレッド ID を開いた値のこと
    // ファイル名とファイルハンドルと考えるとわかりやすいかな
    // ファイル名と違いスレッド ID は実コード上であまり役に立つ場面が無い
    HANDLE hThread=(HANDLE)_beginthreadex(0, 0U, threadentryfunc, &arg,
                                          CREATE_SUSPENDED, &threadID);
    wprintf(L"Thread handle %p Created but not start\n", hThread);
    ResumeThread(hThread);
    wprintf(L"Thread run\n");
    WaitForSingleObject(hThread, INFINITE);
    GetExitCodeThread(hThread, &exitcode);
    wprintf(L"exitcode=%d\n", exitcode);
    CloseHandle(hThread);
    return 0;
}

なおぞう 2016/02/08(月) 14:41:11
>tetrapod様

丁寧な説明ありがとうございます。
unsignedの件、知識が乏しくてお恥ずかしい限りです。

スレッドの中でファイルのfopen_sなど使っているので、やはり_beginthreadを使うこと
にします。

提示して戴いたソースを早速テストプログラムとして使わせて戴きました。

作成するのはC#アプリから呼びだされる関数内で呼びだされるスレッドです。
スレッドの処理が終わらないうちに、アプリ側に制御を返す仕様です。

よって、

main() //本番では EXPORT 関数

    int arg = 876;
    unsigned threadID; // スレッド ID 
    DWORD exitcode;

    HANDLE hThread = (HANDLE)_beginthreadex(
       0,
       0U, 
       threadentryfunc,
        &arg,
        0, //←すぐにスレッド実行させたいので0にしました
       &threadID);

    wprintf(L"Thread handle %p Created but not start\n", hThread);
    //ResumeThread(hThread); //コメントアウト(すぐに実行にさせているので)
    wprintf(L"Thread run\n");
    //WaitForSingleObject(hThread, INFINITE); //スレッド処理終了を知る必要が無いの
でコメントアウト

    //wprintf(L"Thread Wait %p ThreadID \n", hThread);

    GetExitCodeThread(hThread, &exitcode);
    wprintf(L"exitcode=%d\n", exitcode);

    CloseHandle(hThread);
    wprintf(L"Thread  %p CloseHandle  ThreadID \n", hThread);

    return 0;
}

unsigned __stdcall threadentryfunc(void* argp) {
    int arg = *reinterpret_cast<int*>(argp);

    wprintf(L"IN arg=%d\n", arg);
    Sleep(3000);
    wprintf(L"OUT arg=%d\n", arg);
    return 711; // スレッド起動関数が return すると内部で _endthreadex される
}


という感じになりました。
ところで、return 711の711にはどういう意味があるのでしょうか?
ネットで検索したのですがヒット出来なかったです。
return 711;の代わりに _endthreadex(0)でも良いのでしょうか。←return 0 ;と同等?


先日書いたコードでは、スレッドの最中にエラーが出た場合に、ExitProcess(3);と書い
て処理をしてましたが(どこかのサイトで見かけた方法ですが)これでも良いのでしょうか。


GetExitCodeThreadの終了ステータスは当然のように259(0x103)だったのですが、
 STILL_ACTIVE = &H103 以外の終了ステータス値一覧を見つけることができずにいま
す。どこに載っているのでしょう?

tetrapod 2016/02/08(月) 14:58:12
876 にも 711 にも意味は無いっすよ。テキトーな値ということです。
戻り値が取得できていれば
オイラのサンプルそのままだったら 711 と表示されるだろうし
オイラのサンプルの 711 を 18291 に変更したら 18291 になるだろうし
0 にしたら 0 になるっしょ。

って・・・
スレッドは呼び出し側とは別コンテクストで走るわけです。
スレッドが終了して初めて終了コードに意味が発生します。
呼び出し側でスレッドの終了を待たずに GetExitCodeThread しても
STILL_ACTIVE 以外が返却されるわけが無いです。
オイラのサンプルは待ってから終了コードを取得してるでしょ?
あなたのサンプルは待たずに取得してるでしょ? 当然ぢゃん。

1.スレッド起動時にスレッドハンドルを記憶しておき
2.アプリケーション側では適宜スレッドハンドルからスレッド終了状況を問い合わせ
3.終了していたら終了コードでなにか処理
ってやらないと意味ないっす。 CloseHandle は終了コードを取得した後。

> _endthreadex(0)
いりません。というよりやってはいけません。理由は既に書いてます。

> スレッドの最中にエラーが出た場合に、ExitProcess(3);
案件上それでよければ無問題。
はっきり言って論外っしょ。

なおぞう 2016/02/08(月) 15:21:25
> tetrapodさま
何度もありがとうございます。


>呼び出し側でスレッドの終了を待たずに GetExitCodeThread しても
>TILL_ACTIVE 以外が返却されるわけが無いです。

はい、理解しています。ですから、私の書いたコードではSTILL_ACTIVEが返ってきて当然
なのですが、もし、スレッドが終わるのを待っていたら、STILL_ACTIVE以外の値が返って
くるのだろうと思い、だとしたらどういう変数名で定義されているのかと思っただけなの
です。

リタン値711意味のないものでしたか。あまりに意外な数値だったので、何か意味がある
値なのかと思ってしまいました。


ここまで教えていただければ大丈夫そうです。ありがとうございます。

なおぞう 2016/02/08(月) 16:43:58
TILL_ACTIVE 以外は、自分が返した値なのですね。

ITO 2016/02/08(月) 18:56:18
tetrapodさん、レス、ありがとうございます。

なおぞう 2016/02/16(火) 14:26:20
[[解決]]
みなさまありがとうございました。
クローズします。

古いスレッドには追加発言できません。