NSRecursiveLock による排他制御を行う : Objective-C プログラミング
PROGRAM
NSRecursiveLock による排他制御を行う
Objective-C には、複数スレッドからの同時アクセスをブロックする機能を持った NSRecursiveLock が利用できます。
この NSLock というクラスは、内部的には POSIX threads の ミューテックス を使った排他制御と同じだそうです。ミューテックスというのは、それで制御されている区間が「使用中か」「未使用か」を判断するための機構です。
類似するロックに NSLock がありますけど、今回扱う NSRecursiveLock では、同一スレッドからの再ロックであれば通過させる「再帰的ロック」という方式を採用しています。
POSIX threads のミューテックスのロックの種類で言えば、この NSRecursiveLock では PTHREAD_MUTEX_RECURSIVE に該当するようです。
ここでは NSRecursiveLock をつかって、複数スレッドからのアクセスをされたくない場所(クリティカルセクション)を保護する方法について記します。
なお、Objective-C で排他制御を行う方法は、他に ミューテックス や、@synchronized や セマフォ、@property の atomic キーワード などがあります。
初期化と最終処理
NSRecursiveLock 利用するために、まずは NSRecursiveLock 型のインスタンス変数を用意します。
@implementation MyClass
{
// NSRecursiveLock を格納する変数を用意します。
NSRecursiveLock* _lock;
}
これを init メソッドなどの、どこか適切な場所で初期化します。
- (id)init
{
self = [super init];
if (self)
{
// NSRecursiveLock インスタンスを生成します。
_lock = [[NSRecursiveLock alloc] init];
}
return self;
}
ARC 環境では Objective-C インスタンスは自動解放されるので、最終処理は必要ありません。
ARC 環境ではない場合には、どこか適切な場所で _lock を release するようにします。
クリティカルセクションを保護する
NSRecursiveLock インスタンスの準備ができたら、それを使って同時アクセスされたくないところを保護します。
- (OtherClass*)value
{
// ロックします。どこかでロックされている場合は、ロックが解かれるまでここで待ちます。
[_lock lock];
@try
{
// 割り込まれたくない処理をここで行います。
return _value;
}
@finally
{
// ロックを解きます。
[_lock unlock];
}
}
- (void)setValue:(OtherClass*)value
{
// ロックします。どこかでロックされている場合は、ロックが解かれるまでここで待ちます。
[_lock lock];
@try
{
// 割り込まれたくない処理をここで行います。
_value = [value copy];
}
@finally
{
// ロックを解きます。
[_lock unlock];
}
}
たとえばこのようにすることで、-setValue: メソッドで値を書き換えている最中に -value メソッドで中途半端な値を取得されないようにすることができます。
なお、今回はロックを解除するのに @try-@finally を使用してみました。
Objective-C の場合、コードによっては例外が発生して途中で処理を抜けてしまう場合があります。@synchronized のときと違って、途中で抜けて unlock が実行されない場合があると、そのロックを解消せずに終わってしまいます。
そのようなことが想定される場合には、このように @finally を使って、抜ける前に必ず unlock を実行する必要があります。
NSRecursiveLock のロック方式
NSRecursiveLock のロック方式は、 ミューテックス の PTHREAD_MUTEX_RECURSIVE と同等です。
同一スレッド内で 2 度目のロックが要求されたときにはブロックせずに素通りさせることができます。そのため、クラス全体に影響するプロパティがあって、それをいろいろなメソッドから呼び出す場合などに向いていると思います。
実行速度は NSLock と比べてわずかに遅くなるようですが、余程の高速ループでもなければ影響はなさそうです。
NSRecursiveLock にも NSLock と同じように、ロックできなかった場合でも解放を待たずに戻り値 NO を返す -tryLock メソッドが用意されているので、別スレッドでのロック状況による制御も出来るようになっています。
また、-lockBeforeDate: メソッドを使って、指定時間内に解放されなかった(タイムアウトした)場合に戻り値 NO を受け取るようにすることもできます。
[ もどる ]