NSLock による排他制御を行う : Objective-C プログラミング

PROGRAM


NSLock による排他制御を行う

Objective-C には、複数スレッドからの同時アクセスをブロックする機能を持った NSLock が利用できます。

この NSLock というクラスは、内部的には POSIX threads の ミューテックス を使った排他制御と同じだそうです。ミューテックスというのは、それで制御されている区間が「使用中か」「未使用か」を判断するための機構です。

 

POSIX threads のミューテックスには、いくつかのロックの種類がありましたが、この NSLock では PTHREAD_MUTEX_NORMAL(同一スレッド上も含めて、どこかでロックしている場合はロックが解消されるのを待つ)に相当する機能を提供するようです。

POSIX threads ミューテックスでの PTHREAD_MUTEX_RECURSIVE(同一スレッド上は除いて、どこかでロックしている場合はロックが解消されるのを待つ)に相当するロックは NSRecursiveLock が提供します。

 

ここでは NSLock をつかって、複数スレッドからのアクセスをされたくない場所(クリティカルセクション)を保護する方法について記します。

なお、Objective-C で排他制御を行う方法は ミューテックス の他にも、@synchronizedセマフォ@property の atomic キーワード などがあります。

初期化と最終処理

NSLock 利用するために、まずは NSLock 型のインスタンス変数を用意します。

@implementation MyClass

{

// NSLock を格納する変数を用意します。

NSLock* _lock;

}

これを init メソッドなどの、どこか適切な場所で初期化します。

- (id)init

{

self = [super init];

 

if (self)

{

// NSLock インスタンスを生成します。

_lock = [[NSLock alloc] init];

}

 

return self;

}

ARC 環境では Objective-C インスタンスは自動解放されるので、最終処理は必要ありません。

ARC 環境ではない場合には、どこか適切な場所で _lock を release するようにします。

クリティカルセクションを保護する

NSLock インスタンスの準備ができたら、それを使って同時アクセスされたくないところを保護します。

- (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 を実行する必要があります。

NSLock のロック方式

NSLock のロック方式は、 ミューテックス の PTHREAD_MUTEX_NORMAL と同等です。

そのため、同一スレッド内であっても 2 度目のロックが登場すると、そこで最初のロックが解放されるのを待ってしまいます。同一スレッドの別の場所で行ったロックが解放されるのを待っていても解放されることがないので、そこでデッドロックします。

 

そんなデッドロックを避けたい場合は、-tryLock メソッドを使って、ロックできなかった場合でも解放を待たずに、戻り値 NO を受け取るようにします。

または -lockBeforeDate: メソッドを使って、指定時間内に解放されなかった(タイムアウトした)場合に、戻り値 NO を受け取るようにすることもできます。

[ もどる ]