NSMutableDictionary
の使用中のスレッドセーフティについて質問があります。
メインスレッドはNSMutableDictionary
からデータを読み取っています。
NSString
ですUIImage
です非同期スレッドが上記の辞書にデータを書き込んでいます(NSOperationQueue
を使用)
上記の辞書スレッドを安全にするにはどうすればよいですか?
NSMutableDictionary
プロパティをatomic
にする必要がありますか?または、追加の変更を行う必要がありますか?
@property(retain) NSMutableDictionary *dicNamesWithPhotos;
NSMutableDictionary
はスレッドセーフなデータ構造になるように設計されておらず、プロパティをatomic
としてマークするだけでは、基になるデータ操作が実際にアトミックに(安全な方法で)実行されることは保証されません。 。
各操作が安全な方法で行われることを保証するには、ロックでディクショナリの各操作を保護する必要があります。
// in initialization
self.dictionary = [[NSMutableDictionary alloc] init];
// create a lock object for the dictionary
self.dictionary_lock = [[NSLock alloc] init];
// at every access or modification:
[object.dictionary_lock lock];
[object.dictionary setObject:image forKey:name];
[object.dictionary_lock unlock];
ロックを保持しながらNSMutableDictionaryへの呼び出しを委任するだけの独自のNSDictionary
をロールすることを検討する必要があります。
@interface SafeMutableDictionary : NSMutableDictionary
{
NSLock *lock;
NSMutableDictionary *underlyingDictionary;
}
@end
@implementation SafeMutableDictionary
- (id)init
{
if (self = [super init]) {
lock = [[NSLock alloc] init];
underlyingDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) dealloc
{
[lock_ release];
[underlyingDictionary release];
[super dealloc];
}
// forward all the calls with the lock held
- (retval_t) forward: (SEL) sel : (arglist_t) args
{
[lock lock];
@try {
return [underlyingDictionary performv:sel : args];
}
@finally {
[lock unlock];
}
}
@end
各操作はロックを待機して保持する必要があるため、スケーラブルではありませんが、場合によっては十分です。
適切なスレッド化されたライブラリを使用する場合は、マルチスレッド化された安全なライブラリであるTKMutableDictionary
があるため、 TransactionKitライブラリ を使用できます。個人的には使っていないのですが、未完成のライブラリのようですが、試してみたくなるかもしれません。
Nsmutabledictionaryを使用する方法は2つあります。
1つは次のとおりです。
NSLock* lock = [[NSLock alloc] init];
[lock lock];
[object.dictionary setObject:image forKey:name];
[lock unlock];
2つは次のとおりです。
//Let's assume var image, name are setup properly
dispatch_async(dispatch_get_main_queue(),
^{
[object.dictionary setObject:image forKey:name];
});
一部の人々が設定と変更可能な辞書の取得を上書きしたい理由を知りません。
今日では、おそらく代わりに@synchronized(object)
を使用します。
...
@synchronized(dictionary) {
[dictionary setObject:image forKey:name];
}
...
@synchronized(dictionary) {
[dictionary objectForKey:key];
}
...
@synchronized(dictionary) {
[dictionary removeObjectForKey:key];
}
NSLock
オブジェクトはもう必要ありません
正解でさえ、エレガントで異なるソリューションがあります:
- (id)init {
self = [super init];
if (self != nil) {
NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self];
self.isolationQueue = dispatch_queue_create([label UTF8String], NULL);
label = [NSString stringWithFormat:@"%@.work.%p", [self class], self];
self.workQueue = dispatch_queue_create([label UTF8String], NULL);
}
return self;
}
//Setter, write into NSMutableDictionary
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = @(count);
}
});
}
//Getter, read from NSMutableDictionary
- (NSUInteger)countForKey:(NSString *)key {
__block NSUInteger count;
dispatch_sync(self.isolationQueue, ^(){
NSNumber *n = self.counts[key];
count = [n unsignedIntegerValue];
});
return count;
}
スレッド安全でないオブジェクトを使用する場合、コピーは重要です。これにより、変数の意図しない解放が原因で発生する可能性のあるエラーを回避できます。スレッドセーフエンティティは必要ありません。
さらに多くのキューがNSMutableDictionaryを使用する場合は、プライベートキューを宣言し、セッターを次のように変更します。
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
- (void)setCount:(NSUInteger)count forKey:(NSString *)key {
key = [key copy];
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = @(count);
}
});
}
重要!
それなしで独自のプライベートキューを設定する必要がありますdispatch_barrier_syncは単純ですdispatch_sync
詳細はこちら 素晴らしいブログ記事 。
少し調査した後、この記事を皆さんと共有したいと思います。
マルチスレッドアプリケーションでコレクションクラスを安全に使用する http://developer.Apple.com/library/mac/#technotes/tn2002/tn2059.html
Notnoopの答えは結局のところ解決策ではないようです。スレッドの観点からは問題ありませんが、いくつかの重要な微妙な点があります。私はここに解決策を投稿しませんが、この記事には良い解決策があると思います。