CMutex の使い方がいまいちわかりません。
どちらの実装が正しいですか?
(どちらもだめ?)
よろしくおねがいします。
【実装A】
// A body of worker thread
//
CThreadFoo::Body()
{
if( m_mutexA.Lock() )
{
m_pMain->CallBack();
m_mutexA.Unlock();
}
}
// Worker thread creation
//
void CMain::Doit()
{
AfxBeginThread(..., pThreadFoo);
}
// Callback function to be called from worker threads
//
void CMain::CallBack() // public
{
// Do something
}
【実装B】
// A body of worker thread
//
CThreadFoo::Body()
{
if( m_mutex.Lock() )
{
m_pMain->CallBack();
m_mutex.Unlock();
}
}
// Worker thread creation
//
void CMain::Doit()
{
AfxBeginThread(..., pThreadFoo);
}
// Callback function to be called from worker threads
//
void CMain::CallBack() // public
{
if( m_mutexB.Lock() )
{
// Do something
m_mutexB.Unlock();
}
}
スミマセン、間違えました。
【実装B】
// A body of worker thread
//
CThreadFoo::Body()
{
m_pMain->CallBack(); ← 何もせず、呼び出す
}
ついでに、教えてください。
void CMain::CallBack()がコールされるとき、
1.そのままコールされる。
2.メモリ上にコピーがつくられ、それがコールされる。
のどちらでしょうか?
(どちらでもない?)
排他処理において排他する対象が明確でない場合、
どの実装が正しいかという問いに対する答えは
「どれも正しいし、どれも正しくない」です。
この例の場合はm_pMainを排他するのかCallBack内で使われる
変数やリソースを排他するのかによって違ってきます。
> void CMain::CallBack()がコールされるとき、
> 1.そのままコールされる。
> 2.メモリ上にコピーがつくられ、それがコールされる。
1です(「そのまま」の定義にもよりますが)。
ただし、スタックとかレジスタがスレッドごとに異なります。
これはMutexとはまったく無関係でスレッドがどういうものか
という理解が必要です。
それと、スレッド間での排他を実現するだけであれば
MutexよりもCriticalSectionの方がパフォーマンスは優れています。
kure さん、ありがとうございます。
的確な回答で、とても参考になりました。疑問が氷解しました。
排他したい部分は、CMain::CallBack()の // Do somethingです。m_pMain1が持っているリソースのプールから使用可能なリソースを配給する処理です。
# mutexのハンドル番号を基に制御しているはずですから、実装Aと実装Bのどちらでもいいような・・・。
> m_pMain1が持っているリソースのプールから使用可能なリソースを配給する処理です。
配給するリソースの排他が必要なケースと
配給する機能をもつものの排他が必要なケースとで
"何を排他すべきか"が変わります。
個人的にですが、
「排他するもの自体に排他する機能を持たせる」
を推奨します。
MutexをはじめとしたWindowsの持つ排他オブジェクトは、
単に排他を実現する機能を提供するだけです。
「何をどのタイミングでいつまで排他するか?」については
設計の段階できちんと精査しておかないと
後々思わぬところでデッドロックに陥ったりしがちです。
気をつけてください。
「排他するもの自体に排他する機能を持たせる」の例を挙げておきます。
例1)長時間または大域的なロックが必要な場合
class ExclusiveLockObject
{
public:
// ロックする
bool Lock() { ... }
// ロックを開放する
void Unlock() { ... }
// 何かする
void Perform() { ... }
};
void Proc(ExclusiveLockObject* obj)
{
obj->Lock();
// 何かする
obj->Perform();
obj->Unlock();
}
例2)短時間または局所的なロックが必要な場合
class ExclusiveLockObject
{
protected:
// object自体をロックする
bool Lock() { ... }
// ロックを開放する
void Unlock() { ... }
public:
// 何かする
void Perform() {
Lock();
...
Unlock();
}
};
void Proc(ExclusiveLockObject* obj)
{
// 何かする
obj->Perform();
}
kureさん、大変詳細な説明をいただき、ありがとうございます。
それで、一点教えていただきたいのですが、bool Lock() の中身はどのように実装すべきでしょうか?
CriticalSection::Lock() などとしていると思われますが、CriticalSectionの実体はどこにあるのでしょうか?
1.プロセスにおいて実体はひとつとして、各スレッドからはポインタで参照する。
2.各スレッドで実体を持つ。
> 1.プロセスにおいて実体はひとつとして、各スレッドからはポインタで参照する。
> 2.各スレッドで実体を持つ。
CriticalSectionはスレッド間の排他のみを実現するための機能を提供します。
従って答えは1になります。
(ただし、CriticalSectionはプロセス内で1つ〜排他対象物の数分あれば良い)
排他制御は排他アクセス可能なものを使って排他される期間を作り出す
ということです。
従って2の方法では排他アクセス可能なものがそれぞれ独立して
存在することになるため、なんら排他にならないことになります。
CriticalSectionの置き場としてはGlobal変数にしてもいいし、
動的にメモリを確保して割り当ててもかまいません。
スレッド間で共通にアクセス可能な場所にあれば問題なしです。
Mutexの場合はそもそもKernelオブジェクトなので、
排他するスレッドごとにOpenMutexすれば良いです。
(実体はKernelが持っているから)
ツイート | ![]() |