スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
[ --/--/-- --:-- ] スポンサー広告 | TB(-) | CM(-)

メータード セクション??

スレッド間の同期について調べてたら、Metered Sectionというものがあるのを知った。
Wikipediaのミューテックスの項目に書いてあったのだが、MSDNの技術文書へのリンクが貼ってあったので、読んでみると。どうも、WinAPIを使って同期オブジェクトが作れるらしい。
同期オブジェクト作るって面白そうだな。
メータードセクションは高速らしいので、早速試してみるが、
「CreateMeteredSectionが定義されていません」って、includeファイルでもあるのかな?と思ってMSDN見てみたが、書いてない。というか技術文書があるだけ??
英語版の方を見たらソースコードが全部書いてあった。
コピペして、コンパイル。。。
早速ベンチマーク。

int g_a;

LPMETERED_SECTION lpMeteredSec;

unsigned WINAPI ThreadFunc(void* lpParam)
{
    for(int i = 0; i < 20000000; i++){
        EnterMeteredSection(CriSec, INFINITE);
        //InterlockedIncrement((LONG*)&g_a);
        g_a++;
        LeaveMeteredSection(CriSec, 1, NULL);
    }
    return 0;
}


この関数をスレッドにして時間計測。同期をInterlocked関数にしたり、クリティカルセクションにしたりしてやってみた。環境は
OS:Windows Vista
MEM: 4GB
CPU: Core 2 Quad Q6600
デバッグビルド
2スレッド

InterlockedIncrement

1335ms

CriticalSection

2960ms

Mutex

280000ms

MeteredSection6100ms

1スレッド
InterlockedIncrement

345ms

CriticalSection

866ms

Mutex

16400ms

MeteredSection

2344ms


上のような結果。Mutexは毎回カーネルモードを経由するので、とっても遅い。メータードセクションはクリティカルセクションの2倍程度だ。メータードセクションは競合した場合のみカーネルモードになるので、クリティカルセクションと似ている。
ソースコードがあるので、じっくり眺めてみた・・・
と、あれれ、これじゃうまく動かないんじゃないか?
というコードを発見してしまった。EnterMeteredSectionとLeaveMeteredSectionの部分

EnterMeteredSection内のWaitForSingleObjectsとReleaseMeteredSectionLockの間が危ない。スレッドが2つまでだったらうまく動くけど、3つ以上になるとロックされたままのスレッドが残っちゃう。もし、ReleaseMeteredSectionLockとWaitForSingleObjectsの間に2つのスレッドがいて、もう1つのスレッドがLeave->Enter->Leaveって呼び出すとSetEventが2回よばれて、lThreadWaitingが0になるけど、実際に待機状態から抜け出せるのは2つのスレッドのうち1つだけ。つまり、n個のスレッドがあったとしたら、n-2個のスレッドは閉じ込められることになる。実際、スレッド3つでやってみると、1つ閉じ込められてしまった。
うーむ、このコードにはバグがあったのか。

でも、どうにか直して使ってみたかったので修正を試みる。問題はSetEventの後、待機中のスレッドがWaitForSingleObjectsを通過しないうちに、次のSetEventが呼ばれてしまうことだ。なので、lThreadWaitingをEnterMeteredSection側のWaitForSingleObjectsの直後にデクリメントさせて、LeaveMeteredSectionではlThreadWaitingがデクリメントされるまで待つことにした。これで、SetEventとスレッドのWaitForSingleObjectsの通過を1対1に対応させることができるので、問題ないように見える。実際、これで3つのスレッドを動作させても、デッドロックはしなくなった。が、速度が激ダウン。。。10倍くらい遅くなってしまった。問題は多分、SetEventの後、待機中のスレッドがWaitFotSingleObjectsから戻るのに時間がかかるため、それを待っているとかなりの時間ロスになってしまうからだろう。LeaveMeteredSectionでスレッドを止めてはいけないようだ。

クリティカルセクションはどうやっているのかと思って、CRITICAL_SECTION構造体をのぞいてみると・・・
セマフォを使っているぞ。でも、セマフォでどうやって…
悩みながら数時間。
セマフォだぁ!!
メータードセクションはイベントを使っていたが、これをセマフォで置き換えてやれば。SetEventが一度呼ばれるとイベントはシグナル状態になる。この状態でさらにSetEventしても状態は変わらない。イベントはAuto-Resetなので、何度SetEventされても、WaitFotSingleObjectsで待機中のスレッドを1つ通せば、非シグナル状態に戻ってしまう。しかし、セマフォを使えばReleaseされた回数をカウントしてるから、Releaseされた分、待機中のスレッドを通過させることができる。LeaveMeteredSectionでスレッドを止めなくても済む。なるほど、クリティカルセクションではセマフォをこういう使い方でしていたんだなぁと納得。(違っていたら教えてください)
イベントをセマフォで置き換え
OpenEvent → OpenSemaphore
CreateEvent → CreateSemaphore
SetEvent → ReleaseSemaphore

書き換え後、3スレッドで実行しても、うまく動くようになった。速度は上のベンチと同じ方法でやって、

2スレッド 6660ms
1スレッド 2348ms

2スレッド時はセマフォを使って待機しなければならないので少し遅くなった。でも、Mutexより格段に速いので問題なし。MeteredSection完成?
スポンサーサイト
[ 2009/06/23 02:30 ] プログラミング | TB(0) | CM(0)

コメントの投稿













管理者にだけ表示を許可する

トラックバック

この記事のトラックバックURL
http://anpcf.blog71.fc2.com/tb.php/7-ac9b98db







上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。