これに関連して answer 、これは正しいのだろうか?
@synchronizedはコードを「スレッドセーフ」にしません
このステートメントをサポートするドキュメントまたはリンクを見つけようとしましたが、成功しませんでした。
これについてのコメントや回答は歓迎します。
スレッドの安全性を高めるために、他のツールを使用できますが、これは私に知られています。
_@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の人はラインの前にいる全員が終了するのを待つ必要があります。
質問の本質は次のとおりだと思います。
同期の適切な使用は、スレッドセーフの問題を解決できますか?
技術的には可能ですが、実際には他のツールを学習して使用することをお勧めします。
以前の知識を前提とせずに答えます。
正しいコードは、仕様に準拠するコードです。良い仕様は
スレッドセーフコードは、複数のスレッドで実行されたときに正しいままのコードです。したがって、
高レベルの要点は次のとおりです。スレッドセーフでは、マルチスレッドの実行中に仕様が満たされる必要があります。これを実際にコーディングするには、たった1つのことを行う必要があります。変更可能な共有状態へのアクセスを規制することです。3。そして、それを行うには3つの方法があります。
最初の2つは簡単です。 3番目の方法では、次のスレッドセーフの問題を防ぐ必要があります。
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不変の共有状態、または可変の非共有オブジェクトは常にスレッドセーフです。
一般的に、_@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);
}
_
これがなぜ起こるのかは明らかです。 foo
とlock
のロックは、フォアグラウンドVSバックグラウンドスレッドで異なる順序で呼び出されます。これは悪い習慣であると言うのは簡単ですが、Foo
がライブラリである場合、ユーザーはコードにロックが含まれていることを知ることはほとんどありません。
@synchronized単独では、コードをスレッドセーフにしませんが、スレッドセーフコードを記述する際に使用されるツールの1つです。
マルチスレッドプログラムでは、複雑な構造の場合に一貫性のある状態を維持し、一度に1つのスレッドだけがアクセスできるようにすることがよくあります。一般的なパターンは、ミューテックスを使用して、構造がアクセスおよび/または変更されるコードの重要なセクションを保護することです。
@synchronized
はthread safe
メカニズムです。この関数内に記述されたコードの一部は、critical section
の一部になり、一度に1つのスレッドのみが実行できます。
@synchronize
はロックを暗黙的に適用しますが、NSLock
はロックを明示的に適用します。
スレッドの安全性のみを保証するものであり、保証するものではありません。つまり、専門のドライバーを車に雇うということですが、それでも車が事故に遭わないという保証はありません。ただし、確率はわずかなままです。
GCD
(グランドセントラルディスパッチ)のコンパニオンはdispatch_once
です。 dispatch_onceは、@synchronized
と同じ働きをします。
@synchronized
ディレクティブは、Objective-Cコードでその場で相互排他ロックを作成する便利な方法です。
相互排他ロックの副作用:
スレッドの安全性は、@synchronized
ブロック。