Perlで少し触れた程度の知識しかないけど、C++でスレッドを使ってみた。
Perlのthreadsライクな呼び出し方ができればいいなーとか思ったけど、
可変長引数の問題があったことを忘れてた。
そもそもC++自体よくわからんちん。
なので今回は簡単にスレッドを使ってみたよ、というだけ。
手元の環境はVC++2008 Express Edition。
スレッドを扱うクラスを作り、そのクラスに関数と引数を1個与えて、
スレッド処理してもらおう、というもの。
ちなみに、エラー処理どころか、ハンドルのCloseも行ってないです。
次書くときはちゃんとかきます。
MyThread.h
#pragma once #include <windows.h> class MyThread { private: HANDLE hThread; DWORD threadId; public: MyThread(void (*)(void*), LPVOID); virtual ~MyThread(); HANDLE getHandle(); DWORD getID(); };
MyThread.cpp
#include "MyThread.h" MyThread::MyThread(void (*threadProc)(void*), LPVOID arg) { hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)threadProc, arg, 0, &threadId ); } MyThread::~MyThread(void) { DWORD param; GetExitCodeThread(hThread , ¶m); // スレッドが生きてる if(param == STILL_ACTIVE){ ExitThread(FALSE); } } HANDLE MyThread::getHandle(){ return hThread; } DWORD MyThread::getID(){ return threadId; }
main.cpp
#include "MyThread.h" #include <iostream> void echoProc(void *p){ int i=3; if(p != NULL){ for(i=0; i<3; ++i){ std::cout << i << " : " << (const char *)p << std::endl; } } } int main(int argc, char**argv){ MyThread t1(echoProc, "Jon"); MyThread t2(echoProc, "Tom"); MyThread t3(echoProc, "Bear"); MyThread t4(echoProc, "Jack"); MyThread t5(echoProc, "Haly"); return 0; }
実行結果
00000 : : : : : JonTomBearJackHaly 11111 : : : : : JonTomBearJackHaly 22222 : : : : : JonTomBearJackHaly
あれれー
スレッドは5つ。だけど出力は1つ。
5つのスレッドが1つしかない標準出力に我先にと文字列を表示させる事をしているので、
文字の並びがバラバラになり、わけわからん結果になる。
ちなみに今回の場合、std::coutへ<<演算子を使って文字列を出力しているので、
出力している間に他のスレッドも処理を進めてしまい、
出力から戻ってきて、次の文字列の出力にそなえている間に、
どんどん文字列が出力されてしまうから。
順序はループ値, " : ", 引数の文字列, std::endlの順なので、
対策には自分の文字列を出力しきるまで待ってもらう。
その仕組みの為にMutexというものがあるので、早速main.cppに実装。
#include "MyThread.h" #include <iostream> HANDLE hEchoMutex; void echoProc(void *p){ int i=3; WaitForSingleObject(hEchoMutex,INFINITE); // Mutexを獲得 if(p != NULL){ for(i=0; i<3; ++i){ std::cout << i << " : " << (const char *)p << std::endl; } } ReleaseMutex(hEchoMutex); // Mutexを手放す } int main(int argc, char**argv){ hEchoMutex = CreateMutex(NULL,FALSE,NULL); MyThread t1(echoProc, "Jon"); MyThread t2(echoProc, "Tom"); MyThread t3(echoProc, "Bear"); MyThread t4(echoProc, "Jack"); MyThread t5(echoProc, "Haly"); return 0; }
再度実行結果
0 : Jon 1 : Jon 2 : Jon 0 : Tom 1 : Tom 2 : Tom 0 : Bear 1 : Bear 2 : Bear 0 : Jack 1 : Jack 2 : Jack 0 : Haly 1 : Haly 2 : Haly
ちゃんと出力されています。
ちなみにこの場合、Jonとかの名前の部分の順番は異なることがあります。
スレッドは5つ実行されており、スレッド5つが1つのMutexを巡っていす鳥ゲームをしているイメージです。
どのスレッドも同じ処理(文字数が違うぐらい)なので、宣言順であるt1,t2,t3,t4,t5の順に行われるとは思いますが、まれに、この順番ではない場合があります。
それはたまたま実行したスレッドに割り当てられたCPUコアが、他のプロセスの処理によって重い状態にある場合等のときに、そのスレッドが遅れることがあります。
ただ、この場合、Mutex獲得してからループを3回行うので、3連続で同じ出力があるのは確実です。
Mutexを獲得する位置と手放す位置を関数の先頭と末尾から、ループ内の先頭と末尾にしてみるとまた違う出力順序になるでしょう。
memo
今のこの実装では、引数を1個しか持てない点が残念。
そこで引数をたくさん持てるArgクラスみたいなのを作って、QtのQStringのフォーマットのように、
MyThread(threadProc, new Arg("hello").arg(1234).arg(new anyClass(NULL));
とかやって、スレッド関数側で、
const char *str = (const char *)arg.pop();
int i = (int)arg.pop();
anyClass *obj= (anyClass *)arg.pop();
とかできるんじゃないかなーとか妄想してます。
シフト演算子をオーバーライドしてもよさそう。
キャストしないとだめっていうのはシャクなんでメソッドにするとかしようかな。
後はvoid*型にすりゃたいていの値は持てるし、データ構造は単方向リストで実装できそうだし、末尾ポインタ持たせとけば計算コストもそうかからんだろうし。
ああ、データの動的確保と開放のタイミングとかもあるから、それもどうにかしないといけないのか。
いちいちコピーするとコストがかかるし、そもそもポインタで受けたら内容物の大きさがわからんのでそれももらわないとコピーするのが難しいし、参照をそのまま渡したほうがラクなんだが、
でもそうなるとそのデータの動的確保されたものを勝手にd開放するとスレッド呼び出し側に不都合がでるかもしれないから、deleteはスレッド関数の最後に自分で定義するのがいいのかな。
上の例ではnew anyClass(NULL)ってやって渡して、anyClass *obj= (anyClass *)arg.pop();とスレッドが受けているので、処理が終わった最後にでもdelete obj;とか。
何いってんのかわからなくなってきた。
あと、この実装では、インスタンスを作った瞬間スレッドを生成するので、
同期実行や遅延実行させる方法を考えないと。
他にも値を返したい時はどうすりゃいいんだ。Perlなら最後に->joinすればいいんだけども。