Objective-Cのスレッドセーフティに関する質問があります。私はいくつかの他の回答、Appleのドキュメントのいくつかを読んだが、これに関してまだ疑問を抱いているので、私は自分の質問をしたいと思った。
私の質問はthree fold:
配列_NSMutableArray *myAwesomeArray;
_があるとします
フォールド1:
間違っている場合は修正しますが、理解していることから、@synchronized(myAwesomeArray){...}
を使用すると、2つのスレッドが同じコードブロックにアクセスできなくなります。したがって、基本的に、次のようなものがある場合:
_-(void)doSomething {
@synchronized(myAwesomeArray) {
//some read/write operation on myAwesomeArray
}
}
_
その後、2つのスレッドがsameメソッドにsameメソッドにアクセスすると、そのコードブロックはスレッドセーフになります。私はこの部分を適切に理解したと思います。
フォールド2:
myAwesomeArray
が異なるメソッドからの複数のスレッドによってアクセスされている場合はどうすればよいですか?私のようなものがある場合:
_- (void)readFromArrayAccessedByThreadOne {
//thread 1 reads from myAwesomeArray
}
- (void)writeToArrayAccessedByThreadTwo {
//thread 2 writes to myAwesomeArray
}
_
現在、2つの異なるスレッドが両方のメソッドに同時にアクセスしています。 myAwesomeArray
に問題がないことを確認するにはどうすればよいですか? NSLockやNSRecursiveLockなどを使用しますか?
フォールド3:
さて、上記の2つのケースでは、myAwesomeArray
はメモリ内のiVarでした。データベースファイルがある場合、常にメモリに保持するとは限りません。データベース操作を実行するたびにdatabaseManagerInstance
を作成し、完了したらリリースします。したがって、基本的に、異なるクラスがデータベースにアクセスできます。各クラスはDatabaseManger
の独自のインスタンスを作成しますが、基本的にはすべて同じ単一のデータベースファイルを使用しています。このような状況で競合状態が原因でデータが破損しないようにするにはどうすればよいですか?
これは、私の基礎のいくつかを明らかにするのに役立ちます。
Fold 1一般的に、_@synchronized
は正しい。ただし、技術的には、コードを「スレッドセーフ」にしません。異なるスレッドが同じロックを同時に取得するのを防ぎますが、重要なセクションを実行するときは常に同じ同期トークンを使用するようにする必要があります。実行しない場合でも、2つのスレッドが同時にクリティカルセクションを実行する状況に陥ることがあります。 ドキュメントを確認してください 。
Fold 2ほとんどの人は、NSRecursiveLockを使用することをお勧めします。私があなただったら、GCDを使用します。 スレッドプログラミングからGCDプログラミングに移行する方法を示す素晴らしいドキュメントです 、この問題へのアプローチはNSLockに基づくアプローチよりもはるかに優れていると思います。簡単に言うと、シリアルキューを作成し、タスクをそのキューにディスパッチします。これにより、クリティカルセクションが連続して処理されることが保証されるため、常に1つのクリティカルセクションのみが実行されます。
Fold 3これはFold 2と同じですが、より具体的です。データベースはリソースであり、多くの場合、配列または他のものと同じです。 データベースプログラミングのコンテキストでGCDベースのアプローチをご覧になりたい場合は、fmdbの実装をご覧ください 。 Fold2で説明したとおりに実行します。
Fold 3の補足として、データベースを使用するたびにDatabaseManagerをインスタンス化してから、それを解放するのが正しいアプローチです。単一のデータベース接続を作成し、アプリケーションセッションを通して保持する必要があると思います。これにより、管理が容易になります。繰り返しになりますが、fmdbはこれをどのように達成できるかを示す素晴らしい例です。
EditGCDを使用したくない場合は、はい、何らかのロック機構を使用する必要があります。はい、NSRecursiveLock
メソッドで再帰を使用する場合、デッドロックを防ぐことができますので、それは良い選択です(@synchronized
)。ただし、キャッチが1つある場合があります。多くのスレッドが同じリソースを待機する可能性があり、アクセスを取得する順序が関連している場合、NSRecursiveLock
は十分ではありません。 NSCondition
でこの状況を管理できますが、この場合GCDを使用すると時間を大幅に節約できます。スレッドの順序が関係ない場合は、ロックで安全です。
Swift 3in WWDC 2016 Session Session 720 Concurrent Programming with GCD in Swift =、queue
を使用する必要があります
class MyObject {
private let internalState: Int
private let internalQueue: DispatchQueue
var state: Int {
get {
return internalQueue.sync { internalState }
}
set (newValue) {
internalQueue.sync { internalState = newValue }
}
}
}
NSMutableArrayをサブクラス化して、アクセサー(読み取りおよび書き込み)メソッドのロックを提供します。何かのようなもの:
@interface MySafeMutableArray : NSMutableArray { NSRecursiveLock *lock; } @end
@implementation MySafeMutableArray
- (void)addObject:(id)obj {
[self.lock lock];
[super addObject: obj];
[self.lock unlock];
}
// ...
@end
このアプローチは、配列の一部としてロックをカプセル化します。ユーザーは、呼び出しを変更する必要はありません(ただし、アクセスがタイムクリティカルな場合は、アクセスをブロック/待機できることに注意する必要があります)。このアプローチの大きな利点は、ロックを使用しないことにした場合、MySafeMutableArrayを再実装してディスパッチキューを使用できること、または特定の問題に最適なものを使用できることです。たとえば、次のようにaddObjectを実装できます。
- (void)addObject:(id)obj {
dispatch_sync (self.queue, ^{ [super addObject: obj] });
}
注:ロックを使用する場合、addObjectのObjective-C実装などは自分自身が再帰的であるため、NSLockではなくNSRecursiveLockが必要になります。