2012-04-15 5 views
3

私の目的とするiOSプログラムがロックしているこの(まれな)奇妙なケースがあります。私がデバッガに侵入すると、2つのスレッドがあり、それらの両方が@synchronized()にスタックされています。@同期化を待ってロックアップしました

私が@同期化を完全に誤解していない限り、私はそれが可能であるとは考えておらず、コマンドの全体的なポイントでもありません。

メインスレッドとワーカースレッドの両方がsqliteデータベースにアクセスする必要があるので、@synchronized(myDatabase)ブロックのdbにアクセスするコードのまとまりをラップします。 dbアクセスを除いて、これらのブロックではそれほど多くは発生しません。

私はまた、sqliteにアクセスするためにFMDatabaseフレームワークを使用していますが、私はそれが重要かどうかわかりません。

myDatabaseは、FMDatabaseオブジェクトを含むグローバル変数です。それはプログラムの開始時に一度作成されます。

+0

_dataLock = [[NSNumber numberWithInt:1] retain] 

を変更します - あなたに@synchronizedを交換してください[ロックをロック]と共有NSLockインスタンス上で[ロック解除をロック]。 – Stavash

+0

これは簡単なことです。 :-)このロックアップは約1週間に1回起こり、その間に何時間も使用されます。あなたが言及しているのは、@ synchronisedが内部的に行うことです。 –

+0

まあ、正確ではありません。 1つは大幅に高速です。 http://perpendiculo.us/?p=133を見てください – Stavash

答えて

5

私はこれでパーティーに遅刻していることを知っていますが、@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. スレッド1は、-printEightと入力してロックを取得します。
  2. スレッド2は-printSomethingElseになり、ロックを取得しようとします。ロックはスレッド1によって保持されるため、ロックが使用可能になり、ブロックされるまで待機するようにエンキューされます。
  3. スレッド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."); 
+0

ポイントA、B、Cでサンプルコード(+ [NSThread sleepForTimeInterval:]が0.1から20.0まで)を使用しても問題はまったく再現できません(iOS SDK7.1とOS X SDK 10.9を使用)。問題を再現する方法を具体的にすることはできますか? –

+0

これを再現することはできません(iOS 9とXcode 7を使用)。とにかく、 '@ synchronized'は同じオブジェクト上の複数のロックで「一般的に」安全であるようには設計されていませんでした。複数のロック(同じスレッド上)で*完全に*安全であるように設計されています。もしこれが実証可能な問題である場合は、Appleに報告してください(エラーを実証するための完全な作業プログラム)をバグレポートとして提出することは非常に良いでしょう。 –

+0

これはまだ注目を集めていると私は本当に驚いています。私はもともと、何年も前にiOS 4.xと5.xでこの問題に気付きました。これは2013年に書き直されました。それ以来、コンパイラとObjective Cランタイムの改良により解決されるかもしれません。 – Holly

0

私はそれが結局、このような基本的なものですので、@synchronized(_dataLock)は、それが行うことになっているものと仮定し、最近、このにつまずきました。

私は自分のデザインで、私は、私は単にDatabaseのインスタンスごとに_dataLock = [[NSNumber numberWithInt:1] retain]を作成していたので、独立して自分のロックを行いますいくつかのDatabaseオブジェクトを持って、_dataLockオブジェクトを調査に行きました。
[NSNumber numberWithInt:1]は、同じポインタと同じオブジェクトを返します。

つまり、Databaseという1つのインスタンスのローカライズされたロックは、Databaseのすべてのインスタンスに対してグローバルなロックではないと思いました。
もちろん、これは決して意図したデザインではなく、これが問題の原因であったと確信しています。

私はちょうどあなたの問題を絞り込むこと

_dataLock = [[NSUUID UUID] UUIDString] retain] 
+0

"私は変更する"私はあなたがこの問題を解決するかどうか分からないと思うようになります。いずれにしても、実際に試したことはないようです。とにかく私は文が矛盾していると思います。「私は、データベースの1つのインスタンスだけのローカライズされたロックは、すべてのインスタンスのデータベースに対するグローバルなロックではないと思いました。「地元だと思ったのはグローバルではない」と言いたいのですか?それは私にはあまり意味がありません。多分あなたは「私は地元だと思ったのはグローバルです」という意味ですか?もしそうなら、答えを編集して修正してください。ありがとうございました! –

関連する問題