...

マルチスレッドアプリケーションにおける共有データ

by user

on
Category: Documents
2

views

Report

Comments

Transcript

マルチスレッドアプリケーションにおける共有データ
技術文献: テクニカルノート
Search
高度な検索
ログイン | ご入会
目次
マルチスレッドアプリケーションにおける共有データに関する問
題
マルチスレッドアプリケーションにおけるコレクションクラスに
関する問題
例 1 不具合のあるアプリケーション
共有オブジェクトに関する安全策
例 2 不具合を修正したアプリケーション
例 3 簡単に安全性を確保するためのカテゴリ
例 4 NSMutableDictionary のサブクラス
要約
参考文献
ダウンロード
ADC連絡先
このテクニカルノートでは、マルチスレッド
の Cocoa アプリケーションにおいて可変コレ
クションクラス(配列、辞書、集合)を使う
ときに発生する可能性のあるいくつかの問題
について、またこれらの問題を解決するため
のいくつかの方法について、実装を踏まえな
がら説明します。
マルチスレッドの Core Foundation アプリ
ケーションでも同様の問題が生じる可能性が
あります。なぜなら、Cocoa クラスと Core
Foundation クラスは自由に相互利用が可能
だ。このテクニカルノートでは、Core
Foundation のコレクションは扱いませんが、
ここで説明する原則のほとんどは、Core
Foundation にも当てはまります。Core
Foundation の詳細については、参考文献 の
Overview of Core Foundation を参照してくだ
さい。
このテクニカルノートではスレッド処理に関
する一般的な説明はしません。入門知識を得
るには、参考文献のリストを参照してくださ
い。
[2002 年 9 月 10 日]
マルチスレッドアプリケーションにおける共有データに関する問題
2 つのスレッドがデータを共有するときは、必ずデータアクセスを同期化して、同時に同じデータを扱う場合に起こりうるバグを回避しなければなり
ません。
ほとんどの場合、スレッドは、ロックを使用して同期化を行い、ほかのスレッドが同時に同じコードに入るのを防ぎます。 たとえば、次のようにな
ります。
NSLock *statisticsLock; // このオブジェクトが存在するものと仮定する;
...
[statisticsLock lock];
// 2 つのスレッドが同時に
statistics += 1;
// 「statistics」を更新しないようにする
[statisticsLock unlock];
// ロックを解放する
statisticsLock を使用することで、「+=」演算を実行できるスレッドは 一度に 1 つだけであることが保証されます。このロックがなけれ
ば、2 つのスレッドが同時に同じ場所を更新しようとし、一方のスレッドの statistics に対する作業結果が失われます。
先頭に戻る
マルチスレッドアプリケーションにおけるコレクションクラスに関する問題
コードの中でが、可変辞書または可変配列を操作するとき場合、その中身を操作している間だけオブジェクトをロックしてもすべてのバグを防げない
可能性があります。
その理由を理解するために、次のコードについて考えてみましょう(本文書で示すコードはすべて、辞書を使用していますが、配列や集合の場合でも
問題は同じです)。
NSLock *dictionaryLock; // このオブジェクトが存在するものと仮定する
NSDictionary *aDictionary; // このオブジェクトが存在するものと仮定する
NSString *theName;
...
[dictionaryLock lock];
theName = [aDictionary objectForKey: @"name"];
[dictionaryLock unlock];
NSLog (@"the name is '%@'", theName);
このコードは、可変辞書へのアクセスを保護します。しかし、辞書の同時使用を避けるために、プログラムのほかのすべての場所で同じロックを使っ
たとしても、このコードではうまくいかない場合があります。次にその例を示します。
可変コレクションは、オブジェクトを削除するときに、オブジェクトに
release (解放)メッセージを送ります。 このことを念頭に置いて、次
のシーケンスを考えてみましょう。
1.
aDictionary の @"name" キーに対して @"Pat" という名前が含まれているものとします。その保持カウントは 1 です。
objectForKey を送り、その後ロックを解放します。これで
theName 変数の値は、@"Pat" となり、保持カウントは 1 です。
2. A スレッドが上記のコードを実行します。 つまり、ロックを確保し、
3. 次に B スレッドがロックを確保し、辞書を次のように更新します。
[aDictionary setObject: @"Sandy"
forKey: @"name"];
辞書は、キーの前の値である @"Pat" に release メッセージを送ります。最初の保持カウントは 1 だったので、この解放によりカウン
トは 0 になり、文字列
@"Pat" は、割り当てを解除されます。
4. 今度は A スレッドが、NSLog(...) 呼び出しの中で変数
theName を使おうとしますが、この変数は、割り当てを解除された
@"Pat" を参照しているので、クラッシュまたはほかの予期できない結果が生じます。
このシーケンスは、マルチスレッドアプリケーションでコレクションクラスを扱う難しさの典型的な例を示しますが、念頭に置いておくべき問題はこ
れだけではありません。次の例を簡単に見てみましょう。
コードの中でオブジェクトを辞書に追加するとします。辞書は値を保持するので、コードではオブジェクトを追加した後にオブジェクトを解放しま
す。 コードは次のようになります。
NSString *theName; // これが存在するものと仮定する
...
[aDictionaryLock lock];
[aDictionary setObject: theName forKey: KEY];
[theNamerelease]; // 辞書がこれを保持するので、保持する必要はない
[aDictionaryLock unlock];
NSLog (@"the name is '%@'", theName);
このコードの持つ危険性は、前のコードが持つ危険性と似ています。つまり、コードがロックを解除した後で、別のスレッドが辞書を変更し、値を解
放する可能性があります。 ここでも、theName が参照する値は、ロックを解除してから NSLog(...) の中で使うまでの短い間に、割り当て
を解除される可能性があります。
辞書だけでなく、配列および集合に格納されているオブジェクトでも問題は同じです。B スレッドが、A スレッドがポインタを持っているオブジェ
クトを配列や集合から削除すれば、A スレッドがオブジェクトを使おうとしているスコープ内で、オブジェクトの有効性を維持できないかもしれま
せん。
まとめると次のようになります。
複数のスレッドがコレクションオブジェクトを共有するときには、そのコレクションへのアクセスを同期化する必要があります。
コレクションオブジェクトは、オブジェクトを削除する(または置き換える)ときに、オブジェクトに release メッセージを送ります。
1 つのスレッドがコレクションにオブジェクトを追加、またはコレクションからオブジェクトを取得した後に、次のスレッドがそのオブジェ
クトを無効にする可能性があります。
つまり、複数のスレッドがコレクションオブジェクトを共有する場合、コレクションオブジェクトに含まれる値は、1 つのスレッドが値の参
照を保持していたとしても、すべてほかのスレッドと共有されます。
先頭に戻る
例 1:不具合のあるアプリケーション
次のソースコードは、スレッド間でコレクションを共有する際の危険性を示します。可変辞書への同時アクセスを防ぐためにロックを使っていますが
それでも、常にクラッシュします。
#import <Foundation/Foundation.h>
static NSMutableDictionary
static NSLock
*aDictionary = nil;
*aDictionaryLock = nil;
@implementation NSMutableDictionary (Churning)
#define KEY
@"key"
- (void) churnContents;
{
unsigned long
i;
for (i = 0; ; i++)
{
NSAutoreleasePool
*pool;
pool = [[NSAutoreleasePool alloc] init];
[aDictionaryLock lock];
[self setObject: [NSString stringWithFormat: @"%d", i]
[aDictionaryLock unlock];
[pool release];
}
}
@end
#define COUNT
10000
static void doGets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
{
NSObject
*anObject;
//
辞書の値を取得し、その後値にメッセージを送信する
[aDictionaryLock lock];
anObject = [aDictionary objectForKey: KEY];
[aDictionaryLock unlock];
forKey: KEY];
[anObject description];
}
}
static void doSets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
{
NSObject
*anObject;
anObject = [[NSObject alloc] init];
[aDictionaryLock lock];
[aDictionary setObject: anObject
[anObject release];
[aDictionaryLock unlock];
forKey: KEY];
[anObject description];
}
}
int main ()
{
SEL
threadSelector;
[[NSAutoreleasePool alloc] init];
threadSelector = @selector(churnContents);
aDictionary = [NSMutableDictionary dictionary];
aDictionaryLock = [[NSLock alloc] init];
//
辞書の反復処理を開始。
//
あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
[NSThread detachNewThreadSelector: threadSelector
toTarget: aDictionary
withObject: nil];
#if 1 // これはクラッシュするので、これを無効にすれば、doSets() もクラッシュすることを確められる
doGets();
#endif
doSets();
return 0;
}
リスト 1 main1.m (「ダウンロード」セクションから入手可能)
マルチスレッドアプリケーションのバグは断続的にしか発生しないかもしれません。問題を確認するために、このコードを繰り返し実行する必要がな
いように、このアプリケーションは可変辞書に churnContents メッセージを送るスレッドを作成します。この方法では辞書のある値を、オブ
ジェクトで繰り返し置き換えます。
このコードには、doGets() 関数と
doSets() 関数が含まれており、両方ともアプリケーションをクラッシュさせる可能性があります。最初
の関数への呼び出しをコメントアウトすることで、もう一方がクラッシュするかどうかを確かめられます。doGets() 関数は、繰り返し辞書から
値を取得し、その値に description (記述)メッセージを送ります。その結果クラッシュが起きます。
doSets() 関数は、値を辞書に追加し、辞書がそれを保持するという前提でその値を解放します。そのオブジェクトに description を送
ることにより、同様にクラッシュの危険性があります(description を使うことに特に意味はありません。どの方法でも結果は同じです)。
先頭に戻る
共有オブジェクトに関する安全策
コレクションから取得したオブジェクト、またはコレクションに保存したオブジェクトをコードの中で安全に扱うにはどうすればよいのでしょうか。
その 1 つの方法として、オブジェクトに retain (保持) メッセージを送り、その後、つまりオブジェクトの操作が終わったときに、その効果を
相殺する
release メッセージをオブジェクトに送ります。この 2 つの処理の結果、最終的にはオブジェクトに変化はありません。しかし、保持
と解放の間、コードはオブジェクトに対する権利を維持するので、ほかのスレッドによる割り当ての解除を防げます。
この方法の問題点は、release メッセージを送ることを覚えておかなければならないことです。メッセージを送らなければ、オブジェクトの割り
当ては解除されず、「リーク」が起こります。操作終了時まで待って
release を送る代わりに、すぐに autorelease (自動解放)メッ
セージを送ることもできます。この方法を取れば、autorelease のプールが割り当てを解除されたときに、確実にオブジェクトが解放されます。
retain と release は、人にお金を貸してくれるように頼み、返却すると自分で約束するようなものと考えてください。retain と autorelease は、人
にお金を貸してくれるように頼み、相手が本人に返却させると約束するようなものと考えてください。 後者の方法は、帳尻が合うことが保証されて
いるのでより堅実です (ただし、autorelease は、release よりも時間もスペースも余計に必要です)。
上記のコードをより安全にするためには、例 1 の前に示したコードに、次のコード中の太字の 1 行を追加する必要があります。
NSLock *dictionaryLock; // このオブジェクトが存在するものと仮定する
NSDictionary *aDictionary; // このオブジェクトが存在するものと仮定する
NSString *theName;
...
[dictionaryLock lock];
theName = [aDictionary objectForKey: @"name"];
[[theName retain] autorelease]; // オブジェクトをしばらく保持する
[dictionaryLock unlock];
NSLog (@"the name is '%@'", theName);
retain と autorelease を合わせて使って何か変化があるようには思えないかもしれませんが、retain はすぐに有効になる一方
で、autorelease は、retain の効果を後で解除します。これら 2 つのメッセージの効果は最終的には相殺されますが、retain は、
コードがオブジェクトを使っている間オブジェクトの割り当て解除を防ぐ機能を果たします。
先頭に戻る
例 2:不具合を修正したアプリケーション
#import <Foundation/Foundation.h>
static NSMutableDictionary
static NSLock
*aDictionary = nil;
*aDictionaryLock = nil;
@implementation NSMutableDictionary (Churning)
#define KEY
@"key"
- (void) churnContents;
{
unsigned long
i;
for (i = 0; ; i++)
{
NSAutoreleasePool
*pool;
pool = [[NSAutoreleasePool alloc] init];
[aDictionaryLock lock];
[self setObject: [NSString stringWithFormat: @"%d", i]
[aDictionaryLock unlock];
[pool release];
}
}
forKey: KEY];
@end
#define COUNT
10000
static void doGets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
{
NSObject
*anObject;
//
辞書の値を取得し、その後その値にメッセージを送る
[aDictionaryLock lock];
anObject = [aDictionary objectForKey: KEY];
[[anObject retain] autorelease];
[aDictionaryLock unlock];
[anObject description];
}
}
static void doSets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
{
NSObject
*anObject;
anObject = [[NSObject alloc] init];
[aDictionaryLock lock];
[aDictionary setObject: anObject
[anObject autorelease];
[aDictionaryLock unlock];
forKey: KEY];
[anObject description];
}
}
int main ()
{
SEL
threadSelector;
[[NSAutoreleasePool alloc] init];
threadSelector = @selector(churnContents);
aDictionary = [NSMutableDictionary dictionary];
aDictionaryLock = [[NSLock alloc] init];
//
辞書の反復処理を開始。
//
あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
[NSThread detachNewThreadSelector: threadSelector
toTarget: aDictionary
withObject: nil];
doGets();
doSets();
return 0;
}
リスト 2 main2.m(「ダウンロード」セクション から入手可能)。上記のソースコードは、例 1 とは、2、3 行変わっているだけです
が、もはやクラッシュは起こりません。
このコードでは、doGets() 関数と
doSets() 関数は、自身を守るためにコードを追加または変更しています。doGets() 関数は、辞書
から取得するオブジェクトを一時的に保持し、 その後 autorelease によりその保持効果を相殺します。 deSets 関数は、release を
autorelease に変更します。
辞書を反復処理するスレッドのコードが前と同じだということに注意してください。 このスレッドはオブジェクトを辞書から取り出すことも、また
辞書に保存した値を使うこともありません。
データを安全に保つ役目を、このスレッドに担ってもらわないのはどうしてなのでしょうか。スレッドに、除去しようとしているオブジェクトの
retain と autorelease を実行する役目を担ってもらわないのはどうしてでしょうか。なぜなら、各スレッドは各自、autorelease のプールを持って
いるからです。反復処理を実行するスレッドが、オブジェクトに retain、その後 autorelease を送った場合、この autorelease は、反復処理スレッ
ドが自身の autorelease のプールを解放したときに有効になります。つまり、プールの解放は、ほかのスレッドがまだそのオブジェクトを使用して
いるときにも起こる可能性があります。
まとめると次のようになります。
retain と release は、一時的にオブジェクトが割り当てを解除されないようにする働きをしますが、release を送るのを忘れないよう
にしなければなりません。
retain と autorelease は、autorelease のプールが、デベロッパに代わって
release を送ることを覚えておいてくれるので、より機能
的かもしれません。
オブジェクトを保護しようとしているスレッドが、autorelease を行う必要があります。ほかのスレッドだと、不適切なタイミングで、オブ
ジェクトが解放される可能性があります。
先頭に戻る
例 3:簡単に安全性を確保するためのカテゴリ
上記のコードでも目的は達成できますが、辞書を安全に利用するためには、辞書を使うコードごとに、同じ手順を踏まなければなりません。開発者が
安全なコードをより簡単に記述できるようにするために、機能をカプセル化することもできます。その1つの方法は、
NSMutableDictionary にカテゴリを追加することです。そうすることにより、開発者に代わって大半の処理を行う複数のメソッドを追加
できます。
次の3つのメソッドを追加するとします。
- (id) threadSafeObjectForKey: (id) aKey usingLock: (NSLock *) aLock;
- (void) threadSafeRemoveObjectForKey: (id) aKey usingLock: (NSLock *) aLock;
- (void) threadSafeSetObject: (id) anObject
forKey: (id) aKey usingLock: (NSLock *) aLock;
たとえば、コードが次のようになっているとします。
[aDictionaryLock lock];
anObject = [aDictionary objectForKey: KEY];
[[anObject retain] autorelease];
[aDictionaryLock unlock];
カテゴリを追加すれば、上記のコードは、次のようにステートメントを 1 つにできます。
anObject = [aDictionary threadSafeObjectForKey: KEY
usingLock: aDictionaryLock];
次のソースコードには、改訂されたアプリケーションコードに加え、このカテゴリのインタフェース宣言と実装が含まれています(通常は、カテゴリ
のインタフェースはそれ自身の".h"ファイルに、カテゴリの実装はそれ自身の".m"ファイルに入れます。この例では、簡単にするために、すべてを1
つのファイルに入れています)。
#import <Foundation/Foundation.h>
////////////////////////////////////////////////////////////////
////
NSMutableDictionary CATEGORY FOR THREAD-SAFETY
////////////////////////////////////////////////////////////////
@interface NSMutableDictionary (ThreadSafety)
- (id) threadSafeObjectForKey: (id) aKey
usingLock: (NSLock *) aLock;
- (void) threadSafeRemoveObjectForKey: (id) aKey
usingLock: (NSLock *) aLock;
- (void) threadSafeSetObject: (id) anObject
forKey: (id) aKey
usingLock: (NSLock *) aLock;
@end
@implementation NSMutableDictionary (ThreadSafety)
- (id) threadSafeObjectForKey: (id) aKey
usingLock: (NSLock *) aLock;
{
id
result;
[aLock lock];
result = [self objectForKey: aKey];
[[result retain] autorelease];
[aLock unlock];
return result;
}
- (void) threadSafeRemoveObjectForKey: (id) aKey
usingLock: (NSLock *) aLock;
{
[aLock lock];
[self removeObjectForKey: aKey];
[aLock unlock];
}
- (void) threadSafeSetObject: (id) anObject
forKey: (id) aKey
usingLock: (NSLock *) aLock;
{
[aLock lock];
[[anObject retain] autorelease];
[self setObject: anObject forKey: aKey];
[aLock unlock];
}
@end
////////////////////////////////////////////////////////////////
////
TEST PROGRAM
////////////////////////////////////////////////////////////////
static NSMutableDictionary
static NSLock
*aDictionary = nil;
*aDictionaryLock = nil;
@implementation NSMutableDictionary (Churning)
#define KEY
@"key"
- (void) churnContents;
{
unsigned long
i;
for (i = 0; ; i++)
{
NSAutoreleasePool
*pool;
pool = [[NSAutoreleasePool alloc] init];
[self threadSafeSetObject: [NSString stringWithFormat: @"%d", i]
forKey: KEY usingLock: aDictionaryLock];
[pool release];
}
}
@end
#define COUNT
10000
static void doGets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
//
辞書の値を取得し、その後その値にメッセージを送信する
[[aDictionary threadSafeObjectForKey: KEY
usingLock: aDictionaryLock] description];
}
static void doSets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
{
NSObject
*anObject;
anObject = [[NSObject alloc] init];
[aDictionary threadSafeSetObject: anObject
forKey: KEY
usingLock: aDictionaryLock];
[anObject release];
[anObject description];
}
}
int main ()
{
SEL
threadSelector;
[[NSAutoreleasePool alloc] init];
threadSelector = @selector(churnContents);
aDictionary = [NSMutableDictionary dictionary];
aDictionaryLock = [[NSLock alloc] init];
//
//
辞書の反復処理を開始。
あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
[NSThread detachNewThreadSelector: threadSelector
toTarget: aDictionary
withObject: nil];
doGets();
doSets();
return 0;
}
リスト 3 main3.m (「ダウンロード」セクション から入手可能)
これらのメソッドでは、辞書を制御するロックを指定するように要求されます。通常は、辞書ごとに 1 つの
NSLock インスタンスを使うことを選
択します。
ロック指定のもう 1 つの方法は、カテゴリに、自身でロックを提供する
threadSafeObjectForKey: などの、より簡単なメソッドを提
供させることです。この方法では、1 つのロックをすべての辞書に対して使うことになるか、各辞書が個別のロックにマッピングされる 1 つのデー
タ構造体を持つことになるという点で問題があります。データ構造体には、自身のロックが必要なため、どちらにしても、1 つのロックが、すべての
スレッドのボトルネックになる可能性があります。
各辞書にロックを簡単に関連付けるために、辞書クラスをサブクラス化することができます。次の例で、その方法を詳しく説明します。
先頭に戻る
例 4 NSMutableDictionary のサブクラス
カテゴリに
threadSafeObjectForKey のようなメソッドを追加し、アプリケーションを作成するすべてのデベロッパにこのメソッドを
NSMutableDictionary のサブクラスを作成し、 objectForKey: などのメソッドをオーバーラ
使うよう要求する方法の代わりに、
イドして、スレッドセーフな実装で置き換えるという方法もあります。
NSMutableDictionary は、 NSDictionary クラスクラスタの一部です。クラスクラスタの内部でサブクラス化することは少々複雑
なので、十分な理由がない場合は行うべきではありません。クラスクラスタおよびそれらをサブクラス化する方法をこれから学ぶ場合は、クラスクラ
スタの入門手引きを参照してください。
次の実装では、そのクラスクラスタの文献の中で説明されている「複合オブジェクト」技法を使っています。コードでは、可変辞書の実体と 1 つの
ロックを含むオブジェクトを定義します。また、 NSMutableDictionary の各プリミティブメソッドも実装しています。
#import <Foundation/Foundation.h>
////////////////////////////////////////////////////////////////
////
NSMutableDictionary SUBCLASS
////////////////////////////////////////////////////////////////
@interface ThreadSafeMutableDictionary : NSMutableDictionary
{
NSMutableDictionary
*realDictionary;
NSLock
*lock;
}
@end
@implementation ThreadSafeMutableDictionary : NSMutableDictionary
//
NSDictionaryにあるプリミティブメソッド
- (unsigned) count;
{
// このためのロックは必要ないと思われる
return [realDictionary count];
}
- (NSEnumerator *) keyEnumerator;
{
NSEnumerator
*result;
//
この操作のためにロックする必要があるかどうかはっきりしない
//
しかし、慎重になろう
[lock lock];
result = [realDictionary keyEnumerator];
[lock unlock];
return result;
}
- (id) objectForKey: (id) aKey;
{
id
result;
[lock lock];
result = [realDictionary objectForKey: aKey];
// ロックを解除する前に、autorelease プールが解放されるまで、
// このオブジェクトの割り当てを解除されないようにする
[[result retain] autorelease];
[lock unlock];
return result;
}
// NSMutableDictionary のプリミティブメソッド
- (void) removeObjectForKey: (id) aKey;
{
//
このメソッド自身が問題に遭遇することはないかもしれないが、
// ほかのスレッドを損なうことがないようにロックを尊重する
[lock lock];
[realDictionary removeObjectForKey: aKey];
[lock unlock];
}
- (void) setObject: (id) anObject forKey: (id) aKey;
{
//
オブジェクトを辞書に入れるとほかのスレッドによって
// 解放される危険があるので、保護する
[[anObject retain] autorelease];
//
ロックを尊重する。なぜなら、オブジェクトを設定すると
// 前のオブジェクトが解放される可能性があるから
[lock lock];
[realDictionary setObject: anObject forKey: aKey];
[lock unlock];
}
//
これはプリミティブではないが、最適化しよう
- (id) initWithCapacity: (unsigned) numItems;
{
self = [self init];
if (self != nil)
realDictionary = [[NSMutableDictionary alloc] initWithCapacity: numItems];
return self;
}
//
NSObjectに対するオーバーライド
- (id) init;
{
self = [super init];
if (self != nil)
lock = [[NSLock alloc] init];
return self;
}
- (void) dealloc;
{
[realDictionary release];
[lock release];
[super dealloc];
}
@end
////////////////////////////////////////////////////////////////
////
TEST PROGRAM
////////////////////////////////////////////////////////////////
static NSMutableDictionary
*aDictionary = nil;
@implementation NSMutableDictionary (Churning)
#define KEY
@"key"
- (void) churnContents;
{
unsigned long
i;
for (i = 0; ; i++)
{
NSAutoreleasePool
*pool;
pool = [[NSAutoreleasePool alloc] init];
[self setObject: [NSString stringWithFormat: @"%d", i]
[pool release];
}
}
@end
#define COUNT
10000
static void doGets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
//
辞書の値を取得し、その後値にメッセージで送信する
[[aDictionary objectForKey: KEY] description];
}
static void doSets (void)
{
long
i;
for (i = 0; i < COUNT; i++)
{
NSObject
*anObject;
anObject = [[NSObject alloc] init];
[aDictionary setObject: anObject forKey: KEY];
[anObject release];
[anObject description];
}
}
int main ()
{
SEL
threadSelector;
[[NSAutoreleasePool alloc] init];
forKey: KEY];
threadSelector = @selector(churnContents);
aDictionary = [ThreadSafeMutableDictionary dictionary];
//
辞書の反復処理を開始。
//
あるキーに対応する 1 つの値を繰り返し新しい値で置き換える
[NSThread detachNewThreadSelector: threadSelector
toTarget: aDictionary
withObject: nil];
doGets();
doSets();
return 0;
}
リスト 4 main4.m (「ダウンロード」セクション から入手可能)
このサブクラスには、いくつかの問題があります。
ロックに要する時間がパフォーマンスに影響する場合があります。
さらに、どのサブクラスも、一般的には高度に最適化されているアップルの「確定」実装ほどは高速ではありません。
各オブジェクトは、2 つの基本オブジェクトを使って実装され、より多くのメモリを使います。
辞書は、必ずサブクラスからインスタンス化する必要があります。 この最終バージョンでは、辞書は、次のコードで作成されます。
aDictionary = [ThreadSafeMutableDictionary dictionary];
まとめると次のようになります。
カテゴリは、既存クラスを基に新機能をカプセル化する簡単な方法を提供します。
サブクラス化は、メソッドを追加するのではなく、メソッドをオーバーライドすることにより機能を追加するもう1つの方法を提供します。
クラスクラスタ内でのサブクラス化には特別の注意が必要です。
先頭に戻る
要約
NSMutableDictionary インスタンスに関連してスレッドの安全上の問題を解決する 4 つの方法を見てきました(同様の方法
を、NSMutableArray と NSMutableSet にも適用できます)。 各方法にはそれぞれ利点と問題点があります。問題点を常に忘れないよ
うにし、マルチスレッドの環境で操作するときのみこれらのクラスを使います。復習として、利点と問題点を次にまとめます。
例 1 ロックを使用して可変辞書へのアクセスを制御
利点:単純明快なコード
問題点: 当たり前のにようにクラッシュする
例2
retain と autorelease を使用してオブジェクトを保護
利点:クラッシュしない
問題点:クライアントコードは安全のために一定のルールに従う必要あり
autorelease を使うと余分の時間と一時的に余分なスペースが必要
例 3 カテゴリでカプセル化した安全なメソッドを追加
利点: クラッシュしない。クライアントコードの簡素化
問題点:クライアントコードは、新しいメソッドを使用し、ロックオブジェクトを提供する必要あり
autorelease を使うと余分の時間と一時的に余分なスペースが必要
例 4 サブクラスを使ってNSMutableDictionary を安全にする
利点:クラッシュしない。クライアントコード変更の必要なし
問題点: 新しいクラスから辞書をインスタンス化する必要あり
パフォーマンスはアップルの実装に比べ劣る
autorelease を使うと余分の時間と一時的に余分なスペースが必要
先頭に戻る
参考文献
Overview of Programming Topic: Multithreading
Thread Safety
Using Foundation from Multiple Threads
Overview of Core Foundation
先頭に戻る
ダウンロード
このテクニカルノートのPDF版 (80K)
ダウンロード
サンプルコード(8K)
ダウンロード
先頭に戻る
連絡先|プライバシーポリシー
製品のご購入・ご購入相談は、お気軽にアップルストアまで。
0120-APPLE-1(0120-27753-1)
Copyright © 2004 Apple Computer, Inc. All rights reserved.
Fly UP