私はこれでパーティーに遅刻していることを知っていますが、@synchronized
が問題を処理している可能性があります。私はそれが何であるかを知ったら、原因を取り除くためにコードを変更するだけでなく、それに対する解決策もありません。
ここでは、このコードを使用してデモンストレーションします。
- (int)getNumberEight {
@synchronized(_lockObject) {
// Point A
return 8;
}
}
- (void)printEight {
@synchronized(_lockObject) {
// Point B
NSLog(@"%d", [self getNumberEight]);
}
}
- (void)printSomethingElse {
@synchronized(_lockObject) {
// Point C
NSLog(@"Something Else.");
}
}
通常、@synchronized
は再帰的に安全なロックです。したがって、[self printEight]
を呼び出すことはOKであり、デッドロックは発生しません。私が見つけたのは、そのルールの例外です。次の一連のイベントはデッドロックの原因となり、追跡が非常に困難です。
- スレッド1は、
-printEight
と入力してロックを取得します。
- スレッド2は
-printSomethingElse
になり、ロックを取得しようとします。ロックはスレッド1によって保持されるため、ロックが使用可能になり、ブロックされるまで待機するようにエンキューされます。
- スレッド1は
-getNumberEight
を入力し、ロックの取得を試みます。ロックはすでに保持されています。他の誰かがキューに入っているので、スレッド1がブロックします。デッドロック。
この機能性は、@synchronized
を使用した場合、飢餓を拘束したいという意図しない結果であると思われる。ロックは他のスレッドが待機していないときにのみ再帰的に安全です。
コードでデッドロックが発生した場合は、各スレッドの呼び出しスタックを調べて、いずれかのデッドロックされたスレッドがすでにロックを保持しているかどうかを確認します。上記のサンプルコードでは、Point A、B、およびCで長いスリープを追加することにより、ほぼ100%の一貫性でデッドロックを再現することができます。
EDIT:
私は、もはや以前の問題を実証することができるよ、それでも問題が発生し、関連する状況があります。 dispatch_sync
の動的な振る舞いと関係しています。
このコードでは、ロックを再帰的に取得する試みが2回あります。メインキューからバックグラウンドキューへの最初の呼び出し。 2番目はバックグラウンドキューからメインキューに呼び出します。
動作の違いは、ディスパッチキューとスレッドの違いです。最初の例は別のキューを呼び出しますが、スレッドを変更することはないため、再帰的なmutexが取得されます。 2番目はスレッドを変更してキューを変更するため、再帰的なミューテックスを取得できません。
強調するために、この機能は設計上のものですただし、GCDも同様に理解できないものもあります。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSObject *lock = [[NSObject alloc] init];
NSTimeInterval delay = 5;
NSLog(@"Example 1:");
dispatch_async(queue, ^{
NSLog(@"Starting %d seconds of runloop for example 1.", (int)delay);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
NSLog(@"Finished executing runloop for example 1.");
});
NSLog(@"Acquiring initial Lock.");
@synchronized(lock) {
NSLog(@"Acquiring recursive Lock.");
dispatch_sync(queue, ^{
NSLog(@"Deadlock?");
@synchronized(lock) {
NSLog(@"No Deadlock!");
}
});
}
NSLog(@"\n\nSleeping to clean up.\n\n");
sleep(delay);
NSLog(@"Example 2:");
dispatch_async(queue, ^{
NSLog(@"Acquiring initial Lock.");
@synchronized(lock) {
NSLog(@"Acquiring recursive Lock.");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"Deadlock?");
@synchronized(lock) {
NSLog(@"Deadlock!");
}
});
}
});
NSLog(@"Starting %d seconds of runloop for example 2.", (int)delay);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
NSLog(@"Finished executing runloop for example 2.");
と
を変更します - あなたに@synchronizedを交換してください[ロックをロック]と共有NSLockインスタンス上で[ロック解除をロック]。 – Stavash
これは簡単なことです。 :-)このロックアップは約1週間に1回起こり、その間に何時間も使用されます。あなたが言及しているのは、@ synchronisedが内部的に行うことです。 –
まあ、正確ではありません。 1つは大幅に高速です。 http://perpendiculo.us/?p=133を見てください – Stavash