「ひょっとして、これって、スレッド作るのがとっても簡単になるんじゃないの?」と思ったので、遊び始めた。
まず、OpenMP を使わずに、Windows API だけでスレッドを作ってみる。
// http://msdn.microsoft.com/en-us/library/ms682516.aspx より
// #include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <strsafe.h>
#define MAX_THREADS 10
#define BUF_SIZE 255
typedef struct {
int x;
int y;
} ThreadArgument, *PThreadArgument;
DWORD WINAPI threadFunction(LPVOID lpParam)
{
HANDLE hStdout;
PThreadArgument pData;
TCHAR msgBuf[BUF_SIZE];
size_t cchStringSize;
DWORD dwChars;
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdout == INVALID_HANDLE_VALUE) { return 1; }
// 引数をキャストする
pData = (PThreadArgument) lpParam;
// スレッド セーフな関数を使って表示する。
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("パラメータ = %d, %d\n"), pData->x, pData->y);
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwThreadId[MAX_THREADS]; // スレッド ID
HANDLE hThread[MAX_THREADS]; // スレッド ハンドル
ThreadArgument datas[MAX_THREADS];
for (int i = 0; i < MAX_THREADS; ++i) {
dwThreadId[i] = 0;
hThread[i] = NULL;
}
for (int i = 0; i < MAX_THREADS; ++i) {
datas[i].x = i * 100;
datas[i].y = i + 100;
// 生成
hThread[i] = CreateThread( NULL, 0,
threadFunction, &datas[i],
0, &dwThreadId[i]);
if (hThread[i] == NULL) {
for (int j = 0; j < MAX_THREADS; ++j) {
if (hThread[j] != NULL) { CloseHandle(hThread[j]); }
}
ExitProcess(i + 100);
}
}
WaitForMultipleObjects(MAX_THREADS, hThread, TRUE, INFINITE);
for (int i = 0; i < MAX_THREADS; ++i) {
if (hThread[i] != NULL) { CloseHandle(hThread[i]); }
}
return 0;
}
OpenMP を使って、同等の「結果を得られる」コードを書くと、こうなる。
// #include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <strsafe.h>
#define MAX_THREADS 10
#define BUF_SIZE 255
typedef struct {
int x;
int y;
} ThreadArgument, *PThreadArgument;
DWORD WINAPI threadFunction(ThreadArgument arg)
{
HANDLE hStdout;
TCHAR msgBuf[BUF_SIZE];
size_t cchStringSize;
DWORD dwChars;
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdout == INVALID_HANDLE_VALUE) { return 1; }
// スレッド セーフな関数を使って表示する。
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("パラメータ = %d, %d\n"), arg.x, arg.y);
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
#pragma omp parallel for num_threads(MAX_THREADS)
for (int i = 0; i < MAX_THREADS; ++i) {
ThreadArgument arg;
arg.x = i * 100;
arg.y = i + 100;
threadFunction(arg);
}
return 0;
}
threadFunction 関数は同じなので、_tmain 関数がとても短くなっている。
さて、「同じ結果」は得られるが、「同等の処理」ではない。
まず、「#pragma omp parallel」によって、スレッドが作られる。Win-API の方は、for ループの中でスレッドを作っているが、OpenMP の方はループの外で作っていることになる。また、「#pragma omp for」によって、事前に for 文が解析される。事前解析で何回ループするかを判断する。今回はコンパイル時にわかるが、実行時にしかわからないこともある。その時でも、ループを始める前に何回回るかを解析するのは一緒。ループの回数を、作成してあるスレッドの数に、このコードでは等分に分配する。Win-API のコードでは、必ず MAX_THREADS 回のループが回るが、OpenMP では(このコードでは)「ループの回数/スレッドの数」の回数しかループしていない。
さて、「事前に for 文が解析される。事前解析で何回ループするかを判断する。」と書いた。よって、ループ中にループ回数を変化させるようなことをしてはいけない。while 文や do-while 文を使うことは出来ないし、for ブロックから抜ける構文があってもいけない。ループの回数が判定できないような終了条件を書くこともできない。おそらく、for ブロック内で終了条件を変化させるようなことをしてもいけないと思う。これらの多くは、コンパイル時に検出される。
すなわち、for ディレクティブによる並列化は、「同じことを何度もするんでしょ?じゃぁ、複数の人数でやりましょうよ。そうすると、一人一人の仕事量が減り、仕事を完了する時間が早くなるでしょ。」と言うことだ。
が、スレッドを使ってやりたいのは、そんなことなのだろうか?確かにそれもひとつだが、「なぁ、これやっといてぇな。俺、こっちせなあかんねん。」というような事もある。例えば、Java や .NET Framework のランタイムは、ガーベッジコレクションを別のスレッドで動作させている。この様な、スレッド毎に別々の仕事をさせたいときは、どうすればいいのだろう?(続く予定)
投稿日時 : 2010年7月27日 22:43