37

Objective-Cのスレッドセーフについて質問があります。私は他のいくつかの答え、いくつかのAppleのドキュメントを読みましたが、これに関してまだ疑問があるので、私自身の質問をしたいと思いました。

私の質問は3つあります:

配列があるとしましょう。NSMutableArray *myAwesomeArray;

折り1:

間違っている場合は訂正してください。ただし、私が理解していることから、を使用@synchronized(myAwesomeArray){...}すると、2つのスレッドが同じコードブロックにアクセスできなくなります。したがって、基本的に、次のようなものがある場合:

-(void)doSomething {
    @synchronized(myAwesomeArray) {
        //some read/write operation on myAwesomeArray
    }
}

次に、2つのスレッドが同時に同じメソッドにアクセスする場合そのコードブロックはスレッドセーフになります。この部分はきちんと理解できたと思います。

折り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、基本的には、すべて同じ単一のデータベースファイルを使用しています。このような状況で競合状態が原因でデータが破損しないようにするにはどうすればよいですか?

これは、私の基本のいくつかを明確にするのに役立ちます。

4

3 に答える 3

43

フォールド1 一般的に、何が正しいかについてのあなたの理解@synchronizedは正しいです。ただし、技術的には、コードを「スレッドセーフ」にすることはできません。異なるスレッドが同時に同じロックを取得するのを防ぎますが、クリティカルセクションを実行するときは常に同じ同期トークンを使用するようにする必要があります。そうしないと、2つのスレッドが同時にクリティカルセクションを実行する状況に陥ることがあります。ドキュメントを確認してください

フォールド2 ほとんどの人はおそらくNSRecursiveLockを使用することをお勧めします。私があなたなら、GCDを使います。これは、スレッドプログラミングからGCDプログラミングに移行する方法を示す優れたドキュメントです。この問題へのアプローチは、NSLockに基づくアプローチよりもはるかに優れていると思います。簡単に言うと、シリアルキューを作成し、タスクをそのキューにディスパッチします。このようにして、クリティカルセクションが連続して処理されるようにするため、一度に実行されるクリティカルセクションは1つだけになります。

折り目3これは折り目2 と同じですが、より具体的です。データベースはリソースであり、多くの場合、アレイなどと同じです。データベースプログラミングのコンテキストでGCDベースのアプローチを確認したい場合は、fmdbの実装を確認してください。それは私がFold2で説明したことを正確に実行します。

Fold 3の補足として、データベースを使用するたびにDatabaseManagerをインスタンス化してからリリースすることは、正しいアプローチではないと思います。単一のデータベース接続を作成し、アプリケーションセッションを通じてそれを保持する必要があると思います。このようにすると、管理が簡単になります。繰り返しになりますが、fmdbは、これを実現する方法の優れた例です。

編集 GCDを使用したくない場合は、はい、何らかのロックメカニズムを使用する必要があります。またNSRecursiveLock、メソッドで再帰を使用するとデッドロックが防止されるため、これは適切な選択です(によって使用され@synchronizedます)。ただし、キャッチが1つある場合があります。多くのスレッドが同じリソースを待機する可能性があり、アクセスを取得する順序が適切である場合、それNSRecursiveLockだけでは不十分です。でこの状況を管理することはできNSConditionますが、私を信じてください。この場合、GCDを使用すると多くの時間を節約できます。スレッドの順序が関係ない場合は、ロックを使用しても安全です。

于 2012-06-01T16:17:06.453 に答える
5

WWDC2016セッションセッション720のSwift3と同様に、Swift 3のGCDを使用した並行プログラミングでは、次を使用する必要があります。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 }
    }
  }
}
于 2016-06-17T23:27:30.050 に答える
1

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] });
}

注:ロックを使用する場合は、NSLockではなくNSRecursiveLockが必要になります。これは、addObjectなどのObjective-C実装自体が再帰的であることを知らないためです。

于 2012-06-01T16:06:34.080 に答える