2007年4月19日木曜日

Threadを使う

写真の表示がスムーズに動くようになったので、写真の切り替えがもたつかないようにマルチスレッド化した。
写真の表示中に、裏で次の写真ファイルを読み込む処理をする。

Threadの開始は

HANDLE LoaderHandle = CreateThread(NULL,0,LoaderThreead,0,0,(LPDWORD)&ThreadID);

最初の NULL はセキュリティ属性、指定しないのでNULL
次はThreadのスタックサイズ、指定しないので0
3番目は実行する関数名、LoaderThreead
4番目は実行関数に渡すパラメータ、指定しないので0
5番目はThreadをすぐ実行するか?サスペンドした状態のThreadを作るか?を指定する。すぐに実行する場合は0を指定、CREATE_SUSPENDEDで停止状態のThreadが作られる
停止中のThreadはResumeThreadで再開できる。実行中のThreadをSuspendThreadで停止することも出来る

LoaderThreead関数が終了するとThreadも終了するが、CloseHandle()をしないとThreadのオブジェクトが残ってメモリリークとなる


マルチスレッド化したのでレンダリング処理(RenderThread)と写真のロード処理(LoaderThread)が独立して動くようになるので、2つのthread間で協調して動かせるようにEventを使った

具体的には、

  1. RenderThreadは次の写真が必要になるとLoaderThreadが写真のロード処理が完了するまでThreadを停止して待つ
  2. LoaderThreadは写真のロードが完了したことをRenderThreadに伝え、さらに次の写真を読み込む要求がRenderThreadから来るまでThreadを停止して待つ
  3. RenderThreadは写真の表示の合間に次の写真をロードするようにLoaderThreadに要求を出す


といった具合にお互いの状態を連絡しあって処理をする事を同期処理というらしい

この同期にEventという仕組みがOSに用意されている。Eventの説明は検索するといっぱいヒットするので省略

Eventを使うメリットは処理待ちの間Threadが停止するので、ポーリングするより余計なCPUパワーを消費しないで済むことだと思う


Eventを使用するにはCreateEventでイベントを作成してHANDLEを取得する

m_LoadRequest = CreateEvent(NULL, FALSE, FALSE, TEXT("LOADREQUEST"));


Threadと同様にハンドルをクローズする必要がある

CloseHandle(m_LoadRequest);


Eventを待つときは

WaitForSingleObject(m_LoadRequest,INFINITE);


Eventをセットするときは


SetEvent(m_LoadRequest);


今回はイベントを自動リセットとしたのでResetEventを呼ぶ必要は無い

ThreadとEvent関連のソース抜粋


bool m_threadEnd;
HANDLE m_RenderHandle;
HANDLE m_LoaderHandle;
HANDLE m_UpdateEvent;
HANDLE m_LoadCmplete;
HANDLE m_LoadRequest;

void OnCreate(HWND hWnd)
{
WORD ThreadID;

gFrame = 0;
gNextFrame = 0;
pictnow = NULL;
pictload = NULL;

m_LoadRequest = CreateEvent(NULL, FALSE, FALSE, TEXT("LOADREQUEST")); // 自動リセット reset状態
m_LoadCmplete = CreateEvent(NULL, FALSE, FALSE, TEXT("LOADEREVENT")); // 自動リセット reset状態
m_UpdateEvent = CreateEvent(NULL, FALSE, TRUE , TEXT("UPDATEEVENT")); // 自動リセット
m_LoaderHandle = CreateThread(NULL,0,LoaderThreead,0,0,(LPDWORD)&ThreadID);
m_RenderHandle = CreateThread(NULL,0,RenderThreead,0,0,(LPDWORD)&ThreadID);
SetThreadPriority(m_RenderHandle,THREAD_PRIORITY_TIME_CRITICAL);
}

void OnDestroy(HWND hWnd)
{
m_threadEnd = TRUE;
// 停止中のスレッドに終了を伝える為にeventをセットする
SetEvent(m_UpdateEvent);
SetEvent(m_LoadRequest);
// ローダースレッドが処理を完了するまで待つ
WaitForSingleObject(m_LoadCmplete,INFINITE);
CloseHandle(m_LoadCmplete);
CloseHandle(m_LoaderHandle);
CloseHandle(m_RenderHandle);
CloseHandle(m_UpdateEvent);
CloseHandle(m_LoadRequest);
}

DWORD WINAPI RenderThreead(LPVOID lpParameter)
{
int frame = -1;
int render_frame = -1;

while(m_threadEnd!=TRUE)
{
// 描画するフレームを決定
if(frame<gFrame)
{
// フレーム更新
frame = gFrame;
}
else
{
// 更新前だが次のフレームを用意しておく
frame = gFrame +1;
}


if(frame>render_frame)
{
render_frame = frame;
if((pictnow==NULL)||(pictnow->IsEnd(frame)))
{
SetThreadPriority(m_LoaderHandle,THREAD_PRIORITY_ABOVE_NORMAL);
WaitForSingleObject(m_LoadCmplete,INFINITE);
if(pictnow)
delete pictnow;
if(pictload==NULL)
break;
pictnow = pictload;
pictload = NULL;

// ローダーを再開する(再開前にスレッドの優先度を下げておく)
SetThreadPriority(m_LoaderHandle,THREAD_PRIORITY_IDLE);
SetEvent(m_LoadRequest);
}
pictnow->Draw(pScreen->m_Buf,pScreen->m_dspW,pScreen->m_dspH,render_frame);
}
else
{
WaitForSingleObject(m_UpdateEvent,INFINITE);
}
}
return 0;
}

DWORD WINAPI LoaderThreead(LPVOID lpParameter)
{
while(m_threadEnd!=TRUE)
{
// 写真のファイル名を取得
TCHAR *fname = FindFile();
if(fname ==NULL)
{
// 取得できなかった
// SendMessage();
pictload = NULL;
break;
}

// ファイルを読み込む
pictload = new CPicture();
if(!pictload->LoadPicture(fname,gNextFrame))
{
// ファイルが読めなかった
continue;
}

// 次のファイルの開始フレームを決定
gNextFrame = pictload->frame_end;

// RenderThread にロード完了したことを伝える
SetEvent(m_LoadCmplete);

// さらに次のファイルを読み込みを開始できるまで待つ
WaitForSingleObject(m_LoadRequest,INFINITE);

}

// スレッド終了待ちを解除するため
SetEvent(m_LoadCmplete);
return 0;
}

0 件のコメント: