web-dev-qa-db-ja.com

@synchronizedはスレッドの安全性を保証しますか?

これに関連して answer 、これは正しいのだろうか?

@synchronizedはコードを「スレッドセーフ」にしません

このステートメントをサポートするドキュメントまたはリンクを見つけようとしましたが、成功しませんでした。

これについてのコメントや回答は歓迎します。

スレッドの安全性を高めるために、他のツールを使用できますが、これは私に知られています。

29
Anoop Vaidya

_@synchronized_は、適切に使用されている場合、コードスレッドを安全にします。

例えば:

スレッドセーフでないデータベースにアクセスするクラスがあるとしましょう。クラッシュを引き起こす可能性が高いため、データベースの読み取りと書き込みを同時に行いたくありません。

したがって、2つの方法があるとしましょう。 storeData:およびLocalStoreと呼ばれるシングルトンクラスのreadData。

_- (void)storeData:(NSData *)data
 {
      [self writeDataToDisk:data];
 }

 - (NSData *)readData
 {
     return [self readDataFromDisk];
 }
_

さて、これらの各メソッドを次のように独自のスレッドにディスパッチする場合:

_ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      [[LocalStore sharedStore] storeData:data];
 });
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      [[LocalStore sharedStore] readData];
 });
_

クラッシュする可能性があります。ただし、_@synchronized_を使用するようにstoreDataメソッドとreadDataメソッドを変更した場合

_ - (void)storeData:(NSData *)data
 {
     @synchronized(self) {
       [self writeDataToDisk:data];
     }
 }

 - (NSData *)readData
 { 
     @synchronized(self) {
      return [self readDataFromDisk];
     }
 }
_

これで、このコードはスレッドセーフになります。 _@synchronized_ステートメントの1つを削除すると、コードはスレッドセーフではなくなることに注意することが重要です。または、selfの代わりに異なるオブジェクトを同期する場合。

_@synchronized_は、同期しているオブジェクトに相互排他ロックを作成します。つまり、コードが@synchronized(self) { }ブロック内のコードにアクセスしたい場合、同じブロック内で実行されているすべての以前のコードの背後に並ぶ必要があります。

異なるlocalStoreオブジェクトを作成する場合、@synchronized(self)は各オブジェクトを個別にロックダウンするだけです。それは理にかなっていますか?

このように考えてください。たくさんの人が別々の行で待っています。各行には1〜10の番号が付けられています。各行で待機する行を選択できます(行ごとに同期することにより)。または_@synchronized_を使用しない場合は、先頭に直接ジャンプしてすべての行をスキップできます。ライン1の人はライン2の人が終了するのを待つ必要はありませんが、ライン1の人はラインの前にいる全員が終了するのを待つ必要があります。

39
Jack Freeman

質問の本質は次のとおりだと思います。

同期の適切な使用は、スレッドセーフの問題を解決できますか?

技術的には可能ですが、実際には他のツールを学習して使用することをお勧めします。


以前の知識を前提とせずに答えます。

正しいコードは、仕様に準拠するコードです。良い仕様は

  • 状態を制約する不変式、
  • 操作の効果を説明する前提条件と事後条件。

スレッドセーフコードは、複数のスレッドで実行されたときに正しいままのコードです。したがって、

  • 操作のシーケンスが仕様に違反することはありません。1
  • クライアントによる追加の同期を必要とせずに、マルチスレッド実行中に不変条件と条件が保持されます2

高レベルの要点は次のとおりです。スレッドセーフでは、マルチスレッドの実行中に仕様が満たされる必要があります。これを実際にコーディングするには、たった1つのことを行う必要があります。変更可能な共有状態へのアクセスを規制することです。3。そして、それを行うには3つの方法があります。

  • アクセスを防止します。
  • 状態を不変にします。
  • アクセスを同期します。

最初の2つは簡単です。 3番目の方法では、次のスレッドセーフの問題を防ぐ必要があります。

  • liveness
    • デッドロック:2つのスレッドが、必要なリソースを解放するために相互に永続的に待機することをブロックします。
    • livelock:スレッドは作業中ですが、進行できません。
    • starvation:スレッドは、進行するために必要なリソースへのアクセスを永久に拒否されます。
  • 安全なパブリケーション:パブリッシュされたオブジェクトの参照と状態の両方を同時に他のスレッドから見えるようにする必要があります。
  • 競合状態競合状態は、出力が制御不能なイベントのタイミングに依存する欠陥です。言い換えれば、正しい答えを得ることが幸運なタイミングに依存している場合、競合状態が発生します。複合操作はすべて、競合状態になる可能性があります。例:「check-then-act」、「put-if-absent」。問題の例はif (counter) counter--;であり、いくつかの解決策の1つは@synchronize(self){ if (counter) counter--;}です。

これらの問題を解決するには、_@synchronize_、揮発性、メモリバリア、アトミック操作、特定のロック、キュー、シンクロナイザー(セマフォ、バリア)などのツールを使用します。

そして質問に戻って:

@synchronizeを適切に使用すれば、スレッドセーフな問題を解決できますか?

上記のツールはすべて_@synchronize_でエミュレートできるため、技術的には可能です。しかし、それはパフォーマンスの低下につながり、活力に関連する問題の可能性を高めます。代わりに、状況ごとに適切なツールを使用する必要があります。例:

_counter++;                       // wrong, compound operation (fetch,++,set)
@synchronize(self){ counter++; } // correct but slow, thread contention
OSAtomicIncrement32(&count);     // correct and fast, lockless atomic hw op
_

リンクされた質問の場合は、実際に_@synchronize_を使用するか、GCD読み取り/書き込みロックを使用するか、ロックストリッピングを使用してコレクションを作成できます。正しい答えは、使用パターンによって異なります。どんな方法でも、提供するスレッドセーフな保証をクラスで文書化する必要があります。


1つまり、無効な状態のオブジェクトを参照するか、事前/事後条件に違反します。

2たとえば、スレッドAがコレクションXを繰り返し、スレッドBが要素を削除すると、実行がクラッシュします。クライアントはX(synchronize(X))の固有ロックで排他的アクセスを行うために同期する必要があるため、これはスレッドセーフではありません。ただし、イテレータがコレクションのコピーを返す場合、コレクションはスレッドセーフになります。

3不変の共有状態、または可変の非共有オブジェクトは常にスレッドセーフです。

22
Jano

一般的に、_@synchronized_はスレッドの安全性を保証しますが、正しく使用した場合のみです。ロックを再帰的に取得することも安全です。ただし、制限はありますが、答えは here です。

_@synchronized_を間違って使用する一般的な方法がいくつかあります。これらは最も一般的です:

_@synchronized_を使用して、アトミックオブジェクトを作成します。

_- (NSObject *)foo {
    @synchronized(_foo) {
        if (!_foo) {
            _foo = [[NSObject alloc] init];
        }
        return _foo;
    }
}
_

__foo_はロックが最初に取得されるとnilになるため、ロックは発生せず、複数のスレッドが最初の完了前に独自の__foo_を作成する可能性があります。

_@synchronized_を使用して、毎回新しいオブジェクトをロックします。

_- (void)foo {
    @synchronized([[NSObject alloc] init]) {
        [self bar];
    }
}
_

このコードと、C#で同等のlock(new object()) {..}をかなり見ました。毎回新しいオブジェクトをロックしようとするため、コードの重要なセクションに常に許可されます。これはある種のコードマジックではありません。スレッドの安全性を確保するために何もしません。

最後に、selfをロックします。

_- (void)foo {
    @synchronized(self) {
        [self bar];
    }
}
_

それ自体は問題ではありませんが、コードが外部コードを使用している場合、またはそれ自体がライブラリである場合、問題になる可能性があります。内部的にはオブジェクトはselfとして知られていますが、外部には変数名があります。外部コードが@synchronized(_yourObject) {...}を呼び出し、@synchronized(self) {...}を呼び出すと、デッドロック状態に陥ることがあります。ロックする内部オブジェクトを作成して、オブジェクトの外部に公開されないようにすることをお勧めします。 init関数内に__lockObject = [[NSObject alloc] init];_を追加することは、安価で簡単で安全です。

編集:

私はまだこの投稿についてよくある質問を受け取っているので、実際に@synchronized(self)を使用するのが悪い考えである理由の例を次に示します。

_@interface Foo : NSObject
- (void)doSomething;
@end

@implementation Foo
- (void)doSomething {
    sleep(1);
    @synchronized(self) {
        NSLog(@"Critical Section.");
    }
}

// Elsewhere in your code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Foo *foo = [[Foo alloc] init];
NSObject *lock = [[NSObject alloc] init];

dispatch_async(queue, ^{
    for (int i=0; i<100; i++) {
        @synchronized(lock) {
            [foo doSomething];
        }
        NSLog(@"Background pass %d complete.", i);
    }
});

for (int i=0; i<100; i++) {
    @synchronized(foo) {
        @synchronized(lock) {
            [foo doSomething];
        }
    }
    NSLog(@"Foreground pass %d complete.", i);
}
_

これがなぜ起こるのかは明らかです。 foolockのロックは、フォアグラウンドVSバックグラウンドスレッドで異なる順序で呼び出されます。これは悪い習慣であると言うのは簡単ですが、Fooがライブラリである場合、ユーザーはコードにロックが含まれていることを知ることはほとんどありません。

9
Holly

@synchronized単独では、コードをスレッドセーフにしませんが、スレッドセーフコードを記述する際に使用されるツールの1つです。

マルチスレッドプログラムでは、複雑な構造の場合に一貫性のある状態を維持し、一度に1つのスレッドだけがアクセスできるようにすることがよくあります。一般的なパターンは、ミューテックスを使用して、構造がアクセスおよび/または変更されるコードの重要なセクションを保護することです。

4
progrmr

@synchronizedthread safeメカニズムです。この関数内に記述されたコードの一部は、critical sectionの一部になり、一度に1つのスレッドのみが実行できます。

@synchronizeはロックを暗黙的に適用しますが、NSLockはロックを明示的に適用します。

スレッドの安全性のみを保証するものであり、保証するものではありません。つまり、専門のドライバーを車に雇うということですが、それでも車が事故に遭わないという保証はありません。ただし、確率はわずかなままです。

GCD(グランドセントラルディスパッチ)のコンパニオンはdispatch_onceです。 dispatch_onceは、@synchronizedと同じ働きをします。

3
user3693546

@synchronizedディレクティブは、Objective-Cコードでその場で相互排他ロックを作成する便利な方法です。

相互排他ロックの副作用:

  1. デッドロック
  2. 飢star

スレッドの安全性は、@synchronizedブロック。

1
Parag Bafna