分身というのは、違う気がする。
parallel
ディレクティブがあるだけだと、複数のスレッドがそこを実行する。
//#include "stdafx.h"
#define _WIN32_WINNT 0x0500
#include <stdio.h>
#include <locale.h>
#include <tchar.h>
#include <omp.h>
#include <strsafe.h>
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
TCHAR msgBuf[BUF_SIZE];
size_t cchStringSize;
DWORD dwChars;
_tsetlocale(LC_ALL, _T("Japanese_Japan"));
#pragma omp parallel shared(hStdout) \
private(msgBuf, cchStringSize, dwChars)
{
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("スレッド%dで実行\n"), omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
return 0;
}
スレッド0で実行
スレッド1で実行
1つのスレッドに1つの実行範囲を割り当てるのが、・・・そういう範囲がありますよという宣言をするのが sections
ディレクティブで、実際に範囲を指定するのが section
ディレクティブ。
//#include "stdafx.h"
#define _WIN32_WINNT 0x0500
#include <stdio.h>
#include <locale.h>
#include <tchar.h>
#include <omp.h>
#include <strsafe.h>
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);;
TCHAR msgBuf[BUF_SIZE];
size_t cchStringSize;
DWORD dwChars;
_tsetlocale(LC_ALL, _T("Japanese_Japan"));
#pragma omp parallel shared(hStdout) \
private(msgBuf, cchStringSize, dwChars)
{
#pragma omp sections
{
#pragma omp section
{
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("セクション1は、%dで1度だけ実行される。\n"),
omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
#pragma omp section
{
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("このセクションの実行は、%dで1度だけ。\n"),
omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
}
}
return 0;
}
セクション1は、0で1度だけ実行される。
このセクションの実行は、1で1度だけ。
当然の疑問。「セクションの数とスレッドの数が違うと、どうなるの?」やって見る。
//#include "stdafx.h"
#define _WIN32_WINNT 0x0500
#include <stdio.h>
#include <locale.h>
#include <tchar.h>
#include <omp.h>
void threadPrint(int num)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
TCHAR msgBuf[BUF_SIZE];
size_t cchStringSize;
DWORD dwChars;
if (hStdout == INVALID_HANDLE_VALUE) {
return;
}
#pragma omp parallel sections num_threads(num) \
shared(hStdout) private(msgBuf, cchStringSize, dwChars)
{
#pragma omp section
{
StringCchPrintf(msgBuf, BUF_SIZE, TEXT("セクション1[%d]\n"),
omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
#pragma omp section
{
StringCchPrintf(msgBuf, BUF_SIZE, TEXT("セクション2[%d]\n"),
omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
#pragma omp section
{
StringCchPrintf(msgBuf, BUF_SIZE, TEXT("セクション3[%d]\n"),
omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
#pragma omp section
{
StringCchPrintf(msgBuf, BUF_SIZE, TEXT("セクション4[%d]\n"),
omp_get_thread_num());
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hStdout, msgBuf, cchStringSize, &dwChars, NULL);
}
}
}
int _tmain(int argc, _TCHAR* argv[])
{
_tsetlocale(LC_ALL, _T("Japanese_Japan"));
for (int i = 1; i <= 6; ++i) {
_tprintf(_T("%d個で呼び出し--------------------\n"), i);
threadPrint(i);
}
return 0;
}
1個で呼び出し--------------------
セクション1[0]
セクション2[0]
セクション3[0]
セクション4[0]
2個で呼び出し--------------------
セクション2[1]
セクション1[0]
セクション4[0]
セクション3[1]
3個で呼び出し--------------------
セクション1[0]
セクション3[2]
セクション2[1]
セクション4[0]
4個で呼び出し--------------------
セクション1[0]
セクション2[1]
セクション3[3]
セクション4[2]
5個で呼び出し--------------------
セクション1[0]
セクション2[1]
セクション3[2]
セクション4[4]
6個で呼び出し--------------------
セクション2[2]
セクション3[4]
セクション1[1]
セクション4[0]
sections
ディレクティブを指定すると、コードは section
ディレクティブの中に書かれなければならない。ただし、最初の section
ディレクティブは、省略が可能。
何回か実行して、スレッドが比較的ばらけた結果を載せた。スレッド数を3個以上にしても、1つのスレッドが全てのセクションを実行することもある。これは、空いているスレッドを使うようなことが行われているためと思われる。なんにしても、どのセクションを何番目に実行させるかは、制御不能。
とっちゃんに質問。PPL では、WriteConsole
関数ではなく、_tprintf
関数が使えると書かれていますね。しかし、実は OpenMP でも、_tprintf
関数は使えます。「スレッドセーフ」という話であれば、POSIX が「全ての関数がスレッドセーフであること」を要求しているので、POSIX に準拠している VC++ は、標準入出力関数はスレッドセーフですから、使っても問題はありません。
そういえば、CRT はソースが付いているので、デバッガーでステップ インをすれば追いかけられるな。では、追いかけてみよう。
まず、cout
。「[VC をインストールしたディレクトリ]\ctr\src\ostream」の737行目辺りに入ってくる。ここの、748行目あたりの const typename _Myos::sentry _Ok(Ostr);
でステップ イン。90行目あたりの、sentry
クラスのコンストラクターに入ってくる。ベース クラスのコンストラクターにステップ イン。70行目あたりの、_Sentry_base
クラスのコンストラクターに来る。ここに、「lock the stream buffer, if there」というコメントがある。「_Myostr.rbuf()->_Lock();
」にステップ イン×2をすると、ファイルが「streambuf」に変わって、171行目辺りにある _Lock
メソッドに入る。このメソッドをさらにステップ インすると、ファイルが「xmutex.cpp」に変わって、ミューテックスによるロックを使っていることがわかる。
次、wprintf
(UNICODE が define されているので)。同じようにステップ インをしていくと、ファイル「_file.c」の260行目あたりから定義されている、_lock_file2
関数から EnterCriticalSection
関数を呼び出していることがわかる。
この様に VC++ の CRT は、ロックによってスレッドセーフにしていることがわかる。それなのに、なぜ元にしたコード(このページ)では、WriteConsole
関数を使っているのか、というのが問題です。なお、WriteConsole
関数に潜り込むことは出来ませんでした。threadPrint
関数を次のように修正して、実行してください。
void threadPrint(int num)
{
#pragma omp parallel num_threads(4)
{
_tprintf(TEXT("セクション0[%d]\n"), omp_get_thread_num());
#pragma omp sections
{
#pragma omp section
_tprintf(TEXT("セクション1[%d]\n"), omp_get_thread_num());
#pragma omp section
_tprintf(TEXT("セクション2[%d]\n"), omp_get_thread_num());
#pragma omp section
_tprintf(TEXT("セクション3[%d]\n"), omp_get_thread_num());
#pragma omp section
_tprintf(TEXT("セクション4[%d]\n"), omp_get_thread_num());
}
}
}
「セクション0」は、全てのスレッドで実行されます。次の1~4のセクションを実行するスレッドが問題です。OpenMP では、全てのセクションを単一のスレッドで実行する場合が多くなります。そして、WriteConsole
関数を使うと、各セクションが別々のスレッドで実行される場合が多くなります。PPL では、どうでしょうか。
あっと、WriteConsole 関数のリファレンスに「WriteFile 関数に似た動作をします
」と書いてある WriteFile 関数のリファレンスには、「この関数は、同期と非同期両方の操作を想定して設計されています。
」と、書かれています。出力をバッファリングして、対象のストリームへ非同期に処理するようです。ということは、「スレッド セーフな関数を使って表示する。 Print the parameter values using thread-safe functions.
」というコメントが、ヘン?この「using thread-safe functions.」というのは、「非同期処理」の意かと思われます。
投稿日時 : 2010年10月24日 14:14