Win32並行処理プログラミング入門11
並行処理プログラミングに於いて、グローバル変数だけが問題を起こすのではありません。グローバル変数を使用しなくとも、リソースを無防備に共有すれば問題が発生します。その事を示すために、複数のスレッドがメンバー変数を書き変えるメソッドを実行するサンプルを用意しました。
#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <process.h>
using namespace std;
//#define ATOMIC //アトミック関数の使用有無
const int threadCount = 64;
class InstanceAndParameter
{
private:
void* ins;
void* param;
public:
InstanceAndParameter( void* ins, void* param )
: ins( ins ), param( param ){}
void* getInstance() const { return ins; }
void* getParameter() const { return param; }
};
class Foo
{
private:
long number;
public:
Foo() : number(0) {}
long getNumber() const { return number; }
//並列的に実行するメソッド
void AddFunc( int count )
{
//アトミックな関数を使用すると正常に加算される
#ifdef ATOMIC
for ( int i = 0; i < count; i++ )
InterlockedExchangeAdd( &number, 1 );
#else
for ( int i = 0; i < count; i++ ) number++;
#endif
}
};
//スレッドから呼び出されるメソッド
static unsigned __stdcall FooCallAddFunc( void* pvParam )
{
//パラメータのチェック
_ASSERTE( FALSE == IsBadReadPtr( pvParam,
sizeof( InstanceAndParameter ) )
&& "スレッドに渡されたパラメータが無効です" );
//インスタンスとパラメータを取り出してメソッドを実行
InstanceAndParameter* ip =
reinterpret_cast< InstanceAndParameter* >( pvParam );
Foo* obj = reinterpret_cast< Foo* >( ip->getInstance() );
int count = PtrToInt(
reinterpret_cast< INT_PTR >( ip->getParameter() ) );
obj->AddFunc( count );
return 0;
}
int _tmain( int, _TCHAR* )
{
//スレッド数の判定
if ( threadCount > MAXIMUM_WAIT_OBJECTS ) {
cout << "【エラー】" << endl;
cout << "指定するスレッド数が多すぎます。" << endl;
cout << "指定するスレッド数は"
<< MAXIMUM_WAIT_OBJECTS
<< "以下にして下さい。" << endl;
return -1;
}
//スレッドのパラメータを用意する
Foo obj;
int count = 100000;
InstanceAndParameter threadparam(
reinterpret_cast< void * >( &obj ),
reinterpret_cast< void * >( ( INT_PTR ) count ) );
//スレッドを作成する
HANDLE hThreads[ threadCount ];
for ( int i = 0; i < threadCount; i++ )
{
hThreads[ i ] = reinterpret_cast< HANDLE >(
_beginthreadex (
__nullptr,
0U,
FooCallAddFunc,
reinterpret_cast< void * >( &threadparam ) ,
CREATE_SUSPENDED, //直ぐには実行しない
__nullptr ) );
}
//作成したスレッドを実行
for ( int i = 0; i < threadCount; i++)
ResumeThread( hThreads[ i ] );
//タイムアウト時間をミリ単位で指定して待機
//※指定した時間は正確ではありません
DWORD result = WaitForMultipleObjects(
threadCount, hThreads, TRUE, 2000 );
//WaitForSingleObject関数が終了した原因を表示
cout << "【スレッドの状態を表示します】" << endl;
unsigned int max = ( WAIT_OBJECT_0 + threadCount - 1 );
if ( result == WAIT_FAILED ) {
//エラーを表示する
LPVOID title = _T( "エラー" );
LPVOID msg;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
__nullptr,
GetLastError(),
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // 既定の言語
reinterpret_cast< LPTSTR >( &msg ),
0,
__nullptr
);
MessageBox( __nullptr,
reinterpret_cast< LPCTSTR >( msg ) ,
reinterpret_cast< LPCTSTR >( title ),
MB_OK | MB_ICONERROR );
LocalFree( msg );
return -1;
}
if ( result == WAIT_TIMEOUT ) {
cout << "タイムアウトしてしまいました。" << endl;
} else if ( ( result >= WAIT_OBJECT_0 ) & ( result < max ) ) {
cout << "全スレッドの処理が終わりました。" << endl;
}
cout << endl;
//グローバル変数の合計値を表示
long correctValue = threadCount * count;
long number = obj.getNumber();
cout << "【グローバル変数の値を表示します】" << endl;
cout << "予想値:" << correctValue << endl;
cout << "実際の値:" << number << endl;
cout << "不足値:" << ( correctValue - number ) << endl;
//ハンドルを閉じる
for ( int i = 0; i < threadCount; i++ )
CloseHandle( hThreads[ i ] );
cout << endl;
return 0;
}
このサンプルも、アトミック関数を使用しないと正常に動作しません。理由は、並行的にメンバー変数を書き変えているからです。共有リソースを複数のスレッドが操作すると、グローバル変数が危険なのとほぼ同じ理由で問題が発生します。続く...