スレッドからスレッドを複数作成

[戻る]
なつき 2003/02/06(木) 11:11:10
おはようございます。なつきといいます。
いつも参考にさせていただいています。

 #すみません、長くなってしまいました。
 #コードを乗せようとしたのですが長くなってしまったので
 #再度送信させていただきます。

現在、以下のようなコードでメインスレッドから
スレッド(スレッドA)を1つ作成し、そのスレッドAから
複数のスレッドを作成して処理を行おうとしています。

ですが、うまく動作しません。
デバックにブレークポイントを置いて
デバックしながらの時と、ブレークポイントを置かず
実行したときでは現象が異なりました・・・。

ブレークポイントを置かない
デバックで追っていく途中に現象としては、

 1. フリーズ(ストール?)の場合
  フリーズは、発生個所は不定で、
  Ctrl+Alt+Delでタスクマネージャーも表示されないため、
    Ctrl+Alt+Delで再起動することになってしまいます。

 2. 例外エラーが発生
  デバックウィンドウに以下のメッセージが表示されます。
    『ASSERT_VALID fails with illegal vtable pointer.
   例外処理 (初回) は TreadFromThread.exe (MFC42D.DLL) にあります: 0xC0000005:
    Access Violation。』
  そのあと、『LRESULT CALLBACK _AfxMsgFilterHook(int code, 
  WPARAM wParam, LPARAM lParam)』関数に飛びます。

スレッドから複数のスレッドを作成することはできるのでしょうか?
また、以下のコードでおかしいところがありましたら
是非、ご教授お願いいたします。

環境:Window2000+SP3 VC6.0

なつき 2003/02/06(木) 11:13:19
なつきです。コードの載せます。
長くなってしまいますが、
ご教授よろしくお願いいたします。

class CTreadFromThreadDlg : public CDialog{
public:
    vector<DWORD>   m_vGetIndex;   //スレッドAとサブスレッド(複数)      
    vector<DWORD>   m_vReturnData;  //スレッドAからメインスレッドに返す
}

BOOL CTreadFromThreadDlg::OnInitDialog()
{
   pMainThread = AfxBeginThread((AFX_THREADPROC)Thread_A,(LPVOID)this,
                                 HREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED,
                                NULL);                

    pMainThread->m_bAutoDelete = FALSE;
    hMainTread = pMainThread->m_hThread;

    pMainThread->ResumeThread();
        
    dwResult = ::WaitForSingleObject(hMainTread, 10000);

    if(dwResult == WAIT_OBJECT_0){
        ::GetExitCodeThread(hMainTread, &dwRet);
        delete pMainThread;
    }
    
    for(DWORD dwdw=0; dwdw<m_vReturnData.size(); dwdw++){
                  //スレッドAがsetした値を取得
        szRet.Format("%d", m_vReturnData.at(dwdw));
        m_list_Data.InsertItem(dwdw, szRet);
    }   
}
UINT CTreadFromThreadDlg::Thread_A(LPVOID pParam)
{
    DWORD dwResult, dwReturn;
    CWinThread *pCpuThread = NULL;
    HANDLE hTread = NULL;
    vector<CWinThread *>  vpWinThread; 
    vector<HANDLE>        vhTread; 
    DWORD dwCnt;

    CTreadFromThreadDlg * pDlg = (CTreadFromThreadDlg *)pParam;

    for(DWORD dw=0; dw<3; dw++)
        pDlg->m_vGetCpuIndex.push_back(dw);

         dwCnt = pDlg->m_vGetCpuIndex.size(); 
    for(dw=0; dw< dwCnt; dw++){

       pCpuThread = AfxBeginThread((AFX_THREADPROC)Thread_sub,    
                  (LPVOID)pDlg,THREAD_PRIORITY_NORMAL,
                      0,CREATE_SUSPENDED, NULL);
            
        pCpuThread->m_bAutoDelete = FALSE;
        hTread = (HANDLE *)pCpuThread->m_hThread;

        //保持
        vpWinThread.push_back(pCpuThread);
        vhTread.push_back(hTread);

        pCpuThread->ResumeThread();

    }
    for(DWORD idx=0; idx<vpWinThread.size(); idx++){

        pCpuThread = vpWinThread.at(idx);

        hTread = vhTread.at(idx);
        dwResult = ::WaitForSingleObject(hTread, 5000);

        if(dwResult == WAIT_OBJECT_0){
            ::GetExitCodeThread(hTread, &dwReturn);
            pDlg->m_vReturnData.push_back(dwReturn);

            delete pCpuThread;
        }
    }//for(idx=0; idx<dwThreadCnt; idx++)

    return TRUE;
}
UINT CTreadFromThreadDlg::Thread_sub(LPVOID pParam)
{

    CTreadFromThreadDlg * pDlg = (CTreadFromThreadDlg *)pParam;
    vector<DWORD>::iterator it;

    DWORD dw = pDlg->m_vGetCpuIndex.at(0);
    it = pDlg->m_vGetCpuIndex.begin();
    pDlg->m_vGetCpuIndex.erase(it);

    return (dw*10)+10;

}

dairygoods 2003/02/06(木) 11:25:37
>    it = pDlg->m_vGetCpuIndex.begin();
>    pDlg->m_vGetCpuIndex.erase(it);

この部分が複数のスレッドによって同時に実行されると
多分予測不能なメモリ破壊が発生します。

クリティカルセクションなどを使って保護する必要があるでしょう。

なつき 2003/02/06(木) 12:07:29
なつきです。
お返事ありがとうございます。

おっしゃるように、クリティカルセクションを
コードに追加してみました。

 1. グローバル変数でCRITICAL_SECTIONを宣言
      CRITICAL_SECTION g_section;
 2. OnInitDialog()の最初のほうで初期化
   InitializeCriticalSection(&g_section);
 3. Thread_sub()の
   DWORD dw = pDlg->m_vGetCpuIndex.at(0); 
         〜
   pDlg->m_vGetCpuIndex.erase(it);
    をEnterCriticalSectionとLeaveCriticalSectionで
    サンドする

そしてデバッグを開始したのですが、最初は機嫌よく
動作していたのですが、4回目あたりに(カウントをはっきり
見れていませんでした・・・)Thread_sub()を抜けたあとに
上記の2. 例外エラーが発生しました。
その後、続けてデバッグを開始すると、
上記1. のだんまり状態になりました。

ただ、そのあとに、ブレークポイントをおかずに
実行すると、正常に動作しました。
スレッドも正常終了しているようです。
(デバッグウィンドウの終了コードで確認しました)

デバッグの方法がおかしいのでしょうか・・・?

らふ 2003/02/06(木) 13:34:19
本筋とは関係ないのですが、

>hTread = (HANDLE *)pCpuThread->m_hThread;

ここのキャストは必要ないのでは?.

PATIO 2003/02/06(木) 21:02:18
今回の件には直接関係無いかもしれませんが、
ウインドウクラスは基本的にスレッドセーフではないので
スレッド間でやり取りしない方が良いかと思います。
単にデータのやり取りをしたいのであれば、
それ用のデータクラスを使って、そのクラス自体に排他処理も組み込んで
しまった方がいいかなとも思います。
データクラスそのものは、親スレッドで確保すればいいわけですし。
CDialogクラスの派生ならCWndクラスから派生しているはずです。
例にあげているソースからでは、実際に問題が発生するようなコードが
動くかどうかは判断できませんが、使わずに済むのであれば
そうするべきだと思います。
ウインドウ自体の制御なんかは、そのウインドウ自身に任せて
そのウインドウにトリガーを与えるようにした方が良いでしょう。

また、スレッドはグローバルな変数に対しては自由にアクセス可能です。
グローバルにデータクラスのインスタンスを置けば、引数で引き渡す
必要はなくなります。もちろん、この場合は、データクラス自身に
排他処理が組み込んでやるか、排他のための仕組みを用意する必要があります。

なつき 2003/02/12(水) 13:28:36
なつきです。

PATIO さん:
お返事ありがとうございます。
また、こちらの返事が遅くなり、申し訳ありません。

>ウインドウクラスは基本的にスレッドセーフではないので

そういったことは、調べていませんでした・・・。
(スレッドセーフという言葉も知りませんでした・・・。)
なんでも、かんでもできるというわけではないんですね。
勉強になります!

まず、データクラスを作成しようと思います。
そして、そのインスタンスをグローバルで
持つようにし、引数で渡さないようにしてみようと思います。

そういった場合、スレッド関数に渡す引数=LPVOID pParamには
何を入れてあげればいいのでしょうか?

また、
>CDialogクラスの派生ならCWndクラスから派生しているはずです。

と、ありますがデータクラスというのは
CWndから派生させるのでしょうか?
あるいは、「genericクラス」で
どこからも派生させなくてもいいでしょうか?

質問ばかりですみませんが、よろしくお願いいたします。

dairygoods 2003/02/13(木) 09:53:47
> そういった場合、スレッド関数に渡す引数=LPVOID pParamには
> 何を入れてあげればいいのでしょうか?

用途が無ければ適当な値を指定しておけばよいです。

> と、ありますがデータクラスというのは
> CWndから派生させるのでしょうか?

CWndが持っている機能を受け継いだ新しいクラスを作りたい時にCWndから派生します。
データクラスが主スレッドとワーカースレッド間のデータ共有の目的で
使用されるのであれば、恐らくCWndの持つ機能は不要と思われますが。


本題とは関係ないですけど、このソースでstd::vectorを使って
行っている処理は、std::stackやstd::queueで実装されていますよ。

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