Enfermé en attente de @synchronized

J'ai ce cas (rare) bizarre où mon programme iOS objective-c se bloque. Quand je pénètre dans le débogueur, il y a deux threads et les deux sont bloqués sur @synchronized ().

À less que je ne sois complètement mal compris, je ne pensais pas que c'était possible et tout le sharepoint la command.

J'ai un thread principal et un thread de travail qui ont tous deux besoin d'accéder à une database sqlite, donc j'enveloppe les blocs de code qui accèdent à la database dans les blocs @synchronized (myDatabase). Il ne se passe pas grand-chose d'autre dans ces blocs, sauf l'access db.

J'utilise aussi le framework FMDatabase pour accéder à sqlite, je ne sais pas si c'est important.

MyDatabase est une variable globale qui contient l'object FMDatabase. Il est créé une fois au début du programme.

Je sais que je suis en retard à la fête avec cela, mais j'ai trouvé une étrange combinaison de circonstances que @synchronized gère mal et est probablement responsable de votre problème. Je n'ai pas de solution, en plus de changer le code pour éliminer la cause une fois que vous savez ce que c'est.

Je vais utiliser ce code ci-dessous pour démontrer.

 - (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."); } } 

En général, @synchronized est un verrou récursif. En tant que tel, appeler [self printEight] est ok et ne provoquera pas d'interblocages. Ce que j'ai trouvé est une exception à cette règle. La série d'events suivante entraînera un blocage et est extrêmement difficile à localiser.

  1. Le thread 1 entre dans -printEight et acquiert le verrou.
  2. Thread 2 entre -printSomethingElse et tente d'acquérir le verrou. Le verrou est tenu par Thread 1, donc il est mis en queue pour attendre que le verrou soit disponible et bloque.
  3. Thread 1 entrez -getNumberEight et tente d'acquérir le verrou. Le verrou est déjà maintenu et quelqu'un d'autre est dans la queue pour l'get ensuite, donc Thread 1 blocks. Impasse.

Il semble que cette fonctionnalité est une conséquence involontaire du désir de @synchronized famine lors de l'utilisation de @synchronized . Le verrou est seulement récursivement sûr quand aucun autre thread ne l'attend.

La prochaine fois que vous rencontrez un interblocage dans votre code, examinez les stacks d'appels sur chaque thread pour voir si l'un ou l'autre des threads bloqués contient déjà le verrou. Dans l'exemple de code ci-dessus, en ajoutant de longues périodes de repos aux points A, B et C, le blocage peut être recréé avec une cohérence de presque 100%.

MODIFIER:

Je ne suis plus en mesure de démontrer le problème précédent, mais il y a une situation connexe qui cause toujours des problèmes. Cela a à voir avec le comportement dynamic de dispatch_sync .

Dans ce code, il y a deux tentatives pour acquérir le verrou récursivement. Les premiers appels de la queue principale dans une queue d'arrière-plan. Le second appelle de la queue d'arrière-plan dans la queue principale.

Ce qui provoque la différence de comportement est la distinction entre les files d'attente d'expédition et les threads. Le premier exemple appelle sur une queue différente, mais ne modifie jamais les threads, de sorte que le mutex récursif est acquis. Le second change les threads lorsqu'il change de queue, de sorte que le mutex récursif ne peut pas être acquis.

Pour souligner, cette fonctionnalité est par design , mais son comportement peut être inattendu pour certains qui ne include pas GCD aussi bien qu'ils le pourraient.

 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."); 

Je suis tombé @synchronized(_dataLock) récemment, en supposant que @synchronized(_dataLock) fait ce qu'il est censé faire, puisque c'est une chose tellement fondamentale après tout.

J'ai continué à étudier l'object _dataLock , dans ma design j'ai plusieurs objects de Database qui feront leur locking de manière indépendante, donc je _dataLock = [[NSNumber numberWithInt:1] retain] simplement _dataLock = [[NSNumber numberWithInt:1] retain] pour chaque instance de Database de Database .
Cependant le [NSNumber numberWithInt:1] returnne le même object, comme dans le même pointeur !!!

Ce qui signifie que ce que je pensais être un verrou localisé pour une seule instance de Database n'est pas un verrou global pour toutes les instances de Database .
Bien sûr, ce n'était jamais la design voulue et je suis sûr que c'était la cause des problèmes.

Je vais changer le

 _dataLock = [[NSNumber numberWithInt:1] retain] 

avec

 _dataLock = [[NSUUID UUID] UUIDSsortingng] retain]