マルチスレッドにおいての引数と戻り値について

ActiveBasicでのプログラミングでわからないこと、困ったことなどがあったら、ここで質問してみましょう(質問を行う場合は、過去ログやWeb上であらかじめ問題を整理するようにしましょう☆)。
返信する
メッセージ
作成者
Canalime
記事: 8
登録日時: 2005年6月03日(金) 13:39

マルチスレッドにおいての引数と戻り値について

#1 投稿記事 by Canalime »

CreateThreadとCloseHandleを用いてサブスレッド化したい関数があるのですが、
CreateThreadでスレッドを作成すると同時に関数が動作するのだと認識しております。
この時、その関数に値を渡したいのですが
普通の関数の時のように値を渡すことは可能でしょうか?
また、スレッド終了の際に戻り値を得たいのですが可能でしょうか?
--/* from Canalime */--  
tak
記事: 162
登録日時: 2005年5月31日(火) 07:49

#2 投稿記事 by tak »

> 普通の関数の時のように値を渡すことは可能でしょうか?

関数に値を渡すことは可能ですが、普通の関数のようにはいきません。
関数に渡せるのは32ビット値が一つだけです。
しかし、これだけでも工夫を凝らせば任意数の値を渡すことはできます。
以下に、解決策を示します。

ポインタ型は32ビット値ですので、CreateThread()を通して関数に渡すことができます。
ポインタを利用して関数に任意数の引数を渡すには、まず呼び出し側が引数リストを作成します。これには、malloc()関数を使うのがよいと思います。
引数リストですが、このリストには“引数へのポインタ”を格納すると、引数の型サイズを意識する必要がなくなります。
引数がByte型だろうがInteger型だろうが、構造体であってもポインタは常に32ビットだからです。
例えば、2個の引数を関数に渡したいとすれば、次のようになります。

呼び出し側のコード

コード: 全て選択

Dim pParam As DWordPtr
pParam = malloc((2 + 1) * 4)

pParam[0] = 2	' 0番目の要素には引数の数を格納すると、何かと便利です
pParam[1] = VarPtr(param1)
pParam[2] = VarPtr(param2)

Dim dummy As DWord	' スレッドのIDが格納される
hThread = CreateThread(ByVal NULL, 0, AddressOf(func), pParam, 0, VarPtr(dummy))

' 以下にコードの続きを記述します
新しいスレッド側のコード
例ではparam1がInteger型、param2がSTRUCT構造体だと仮定します(敢えて32ビット型を避けました)。

コード: 全て選択

Function func(ByVal param As DWordPtr) As DWord
	Dim tempI As *Integer
	Dim p1 As Integer
	Dim p2 As *STRUCT

	tempI = param[1]

	p1 = tempI[0]	' param1
	p2 = param[2]	' param2(ポインタ参照)

' 以下にコードの続きを記述します
なお、pParamはいつかはfree()しなければなりません。
しかし、そのタイミングを間違えないようにしてください。
CreateThread()関数を呼び出した直後に解放してしまうと、新しく生成されたスレッド側で引数を正しく参照できなくなるかもしれません。
タイミングとしては、スレッドが終了する直前にスレッド側で解放するのが間違いがなくベストだと思いますが、その場合DLL化したときにどういった弊害が出るか僕には見当がつきません。
もしかしたら正常に動作するのかもしれませんが、インスタンスが異なるので正常に動作しない可能性があります。実際にテストしたわけではないので何とも言えません。
また、スレッドが終了したら呼び出し側で解放する方法もあります。
どちらにせよ、プログラム終了時に、解放し忘れたオブジェクトをWindowsが自動解放してくれるので、そちらに関してはあまり気を配る必要はないかもしれません。
極端な話、malloc()を何回も呼び出しておいて一回もfree()を使わなかったとしても、Windowsが解放してくれるから問題ないわけです。
大変お行儀の悪いプログラムですが、時折こういうのを見かけます。


> また、スレッド終了の際に戻り値を得たいのですが可能でしょうか?

関数の戻り値はスレッドの終了コードと見なされます。
これは、ExitThread()関数のパラメータに渡す値と同等のものです。
よって、関数の戻り値として使用できないといってよいでしょう。
こちらもポインタを介して値を返すなど工夫が必要です。
その場合、引数リストに戻り値用のポインタを準備しておき、そちらに戻り値を格納するのがよいと思います。ただし、free()する前に値を格納しましょう。
イグトランス
記事: 899
登録日時: 2005年5月31日(火) 17:59
お住まい: 東京都
連絡する:

#3 投稿記事 by イグトランス »

私はスレッド関数の引数用に1つの構造体を作る方がかりやすいと思います。

コード: 全て選択

Type PARAM
	Foo As Long
	Bar As Double
End Type

Function func(p As *PARAM) As DWord
	・・・
End Function
こうして使うときには

コード: 全て選択

Dim Param As PARAM
With Param
	.Foo = 5
	.Bar = -0.25
End With
Dim hHread As HANDLE
Dim ThreadID As DWord
hThread = CreateThread(ByVal NULL, 0, AddressOf(func), Param, 0, VarPtr(ThreadID))
などとするわけです。mallocを使っていないので、freeの呼び出しを考える必要がありません。
ちなみにtakさんの方法では配列の最初の要素に引数の数を代入していましたが、この方法で同じようなことをやるとしたら配列の最初のメンバに構造体のサイズを保持するメンバを用意することになります。
Win32APIの常套手段ですね。
ただしtakさんの方法では擬似可変個引数を再現できるという特長もあります。(マルチスレッドとは無関係に)

CreateThreadで作った関数の戻り値はtakさんの言うとおりExitThread関数の引数にするのと同じです。ただしExitThread()だとコンパイラはただの関数呼び出しとしか見ないのでデストトラクタが呼ばれません。
そのExitTreadに終了コードを渡せるということは受け取る手段があるということに他なりません。そうだからこそExitThreadには引数があると言えます。
その関数はGetExitCodeThread()です。詳しくはヘルプを見て下さい。スレッドハンドルが必要なのでCloseHandle()はGetExitCodeThread()の後まで待って下さい。

ちなみにまだスレッドが終了していないのにこの関数を呼ぶとSTILL_ACTIVEとなりますがこんなコードは勘弁して下さい。

コード: 全て選択

Dim ExitCode As DWord
Do
	GetExitCodeThread(hThread, ExitCode)
Loop While ExitCode = STILL_ACTIVE
WaitForSingleHandle関数を使えばCPUを占領せずにスレッドの終了まで待てます。
Canalime
記事: 8
登録日時: 2005年6月03日(金) 13:39

有り難う御座います

#4 投稿記事 by Canalime »

アドバイスして頂き有り難う御座います。
質問しておいてなんなのですが、サブスレッド化したかった理由は
そのルーチンの実行中はにタスクトレイのアイコンに対する操作が
出来なかったので、サブスレッド化すれば受付できると思ったからです。
ですが、Timerで呼び出しているルーチンなのでサブスレッド化すると
むやみにそのルーチンに入ってしまいそうな気がして只今、再考中です。
そのルーチンでグローバル変数を操作している為、このあたりが無茶苦茶になりそうなので
本来の目的が達成できている以上下手にいじらない方がいいかなと思ってます。

しかし、役立つ情報をいただけたので今後何かに使えればと思います。
--/* from Canalime */--  
返信する