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

PROGRAM


セマフォによる排他制御を行う

Objective-C でも、セマフォという複数スレッドからの同時アクセスをブロックする排他制御が利用できます。

セマフォでは、特定の区間を指定して、その区間が何か所から同時に利用できるようにするかを指定したり、その区間を利用することが「可能か」「不可能か」を判定することができます。

 

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

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

初期化と最終処理

セマフォを利用するために、まずは dispatch_semaphore_t 型のインスタンス変数を用意します。

@implementation MyClass

{

// セマフォを管理する変数を用意します。

dispatch_semaphore_t _semaphore;

}

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

- (id)init

{

self = [super init];

 

if (self)

{

// セマフォを管理する変数を使えるようにします。

_semaphore = dispatch_semaphore_create(1);

}

 

return self;

}

セマフォの初期化では、いくつまで同時アクセスを許可するか指定できるようになっているので、ここでは 1 を指定して、同時に 2 箇所で使用されることがないように制限します。

このとき、同じスレッドからのアクセスであっても 2 回目はブロックされます。同一スレッドでアンロックを待ったところで、解放されることはないのでデッドロックしてしまいます。ロック内から別のメソッドを複雑に呼び出すようなときに、特に注意が必要です。

 

セマフォを使い終わったときの最終処理も、どこか適切な場所で行います。

- (void)dealloc

{

// セマフォを管理する変数を処分します。

dispatch_release(_semaphore);

}

dispatch_release 実行後は、この変数を使ったセマフォ制御はできなくなります。

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

セマフォを管理する変数を用意したら、それを使って同時アクセスされたくないところを保護します。

- (id)value

{

id result;

 

// セマフォを使うことを宣言します。どこかでセマフォが使われている間はここで待ちます。

dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

 

// 割り込まれたくない処理をここで行います。

// ここで値を取得して retain するまでの間に -setValue: メソッドで _value を書き換えられないようにします。

result = [[_value retain] autorelease];

 

// セマフォを使い終わったことを宣言します。

dispatch_semaphore_signal(_semaphore);

 

return result;

}

- (void)setValue:(id)value

{

// セマフォを使うことを宣言します。どこかでセマフォが使われている間はここで待ちます。

dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);

 

// 割り込まれたくない処理をここで行います。

// 代入処理だけですが、この間に -value メソッドで _value を参照されないようにします。

_value = value;

 

// セマフォを使い終わったことを宣言します。

dispatch_semaphore_signal(_semaphore);

}

たとえばこのようにすることで、-setValue: メソッドで値を書き換えている最中に -value メソッドで中途半端な値を取得されないようにすることができます。

セマフォのタイムアウト指定

dispatch_semaphore_wait では、第 2 引数で待ち時間の上限を指定できるようになっています。

待ち時間は、いつのタイミングまで待つかを dispatch_time_t 型 (uint64_t 型) を使って、ナノ秒単位で指定します。待ち時間を無制限にする場合には、ここに DISPATCH_TIME_FOREVER を指定します。

指定するのは、"何ナノ秒後" かではなく、"いつ" かを指定しなければいけないところに注意します。

 

何ナノ秒後かでタイミングでtimeアウトしたい場合には dispatch_time 関数を使ってタイミングを生成できます。

dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, ns);

これで、今から ns ミリ秒後のタイミングを取得することができました。

なお、ミリ秒での指定を手助けするマクロとして、次のものが用意されています。

定義名 意味 実際の値 (unsigned long long) 使い道
NSEC_PER_SEC 1 秒あたりのナノ秒 1,000,000,000 これを 1 秒として、目的のナノ秒数を算出します。たとえば 5 秒であれば "NSEC_PER_SEC * 5" ですし、0.5 秒であれば "NSEC_PER_SEC / 2.0" です。
NSEC_PER_MSEC 1 ミリ秒あたりのナノ秒 1,000,000 これを 1 ミリ秒として、目的のナノ秒を算出します。たとえば 5 ミリ秒であれば "NSEC_PER_MSEC * 5" ですし、0.5 ミリ秒であれば "NSEC_PER_MSEC / 2.0" です。
USEC_PER_SEC 1 秒あたりのマイクロ秒 1,000,000 これを 1 秒として、目的のマイクロ秒を計算できます。
NSEC_PER_USEC 1 マイクロ秒あたりのナノ秒 1,000 これを 1 マイクロ秒として、目的のナノ秒を算出します。たとえば 5 マイクロ秒であれば "NSEC_PER_USEC * 5" ですし、0.5 マイクロ秒であれば "NSEC_PER_USEC / 2.0" です。

これらと乗除算を上手く組み合わせることで、プログラムでどれくらいの時間でタイムアウトするかを指定しているかが、ソースコードを目で見たときに判りやすくなります。

 

なお、dispatch_semaphore_wait がタイムアウトした場合には、戻り値として 0 以外が返されます。

制限時間内にロックできた場合は 0 が返されます。

[ もどる ]