2012-04-15 63 views
3

我有这个(罕见)奇怪的情况下,我的objective-c iOS程序被锁定。当我进入调试器时,有两个线程,它们都卡在@synchronized()中。锁定等待@synchronized

除非我完全误解@synchronized,否则我不认为这是可能的,也是命令的重点。

我有一个主线程和工作线程,都需要访问一个sqlite数据库,所以我打包访问数据库在@synchronized(myDatabase)块中的代码块。除了db访问以外,这些块中没有其他发生。

我也使用FMDatabase框架来访问sqlite,我不知道是否重要。

myDatabase是一个包含FMDatabase对象的全局变量。它在程序开始时创建一次。

+0

只是为了缩小您的问题 - 请尝试更换您的@synchronized到一个共享的NSLock实例[锁锁]和[锁解锁。 – Stavash 2012-04-15 15:53:12

+0

说起来容易做起来难。 :-)这种锁定每周大概发生一次,并在几个小时之间使用。这也是我的理解,你提到的内容正是@synchronized在内部做的。 – 2012-04-15 16:10:20

+0

呃,不完全是。对于一个它显着更快。看看http://perpendiculo.us/?p=133 – Stavash 2012-04-15 16:13:38

回答

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]即可,并且不会造成死锁。我发现的是该规则的例外。以下一系列事件将导致死锁,并且极难追查。

  1. 线程1输入-printEight并获得锁。
  2. 线程2输入-printSomethingElse并尝试获取该锁。锁由线程1保存,因此它被排队等待,直到锁可用并阻塞。
  3. 线程1输入-getNumberEight并尝试获取该锁。锁已被占用,而其他人正在队列中以便接下来,所以线程1阻塞。僵局。

看起来这个功能是在使用@synchronized时希望限制饥饿的意想不到的后果。当没有其他线程正在等待时,该锁只是递归安全的。

下一次您在代码中发生死锁时,请检查每个线程上的调用堆栈,以查看是否有死锁的线程已经拥有锁定。在上面的示例代码中,通过在A,B和C点添加长时间休眠,可以几乎100%一致地重新创建死锁。

编辑:

我不再能证明以前的问题,但有一个相关的情况仍然会造成一些问题。它与dispatch_sync的动态行为有关。

在这段代码中,有两次递归获取锁的尝试。第一次从主队列调用后台队列。第二个从后台调用主队列。

导致行为差异的原因是调度队列和线程之间的区别。第一个示例调用不同的队列,但不会更改线程,因此会获取递归互斥锁。当它改变队列时,第二个改变线程,所以递归互斥体不能被获取。

要强调,此功能是由设计,但它的行为可能是意想不到的一些不理解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)。你能更具体地了解如何重现问题吗? – 2014-07-29 09:12:47

+0

我无法重现这一点(使用iOS 9和Xcode 7)。听起来不正确,但是'@ synchronized'并没有被设计成在同一个对象上具有多个锁的“通常”安全;它被设计成与多个锁完全安全(在同一个线程上)。如果你确实有这样一个可以证明的问题,那么把它提交给苹果公司(完整的工作程序来证明错误)作为错误报告是非常好的。 – 2016-03-30 00:27:36

+0

我真的很惊讶,这仍然受到关注。多年前,我最初在iOS 4.x和5.x上注意到这个问题。这是在2013年写的。从那时起,它可以通过改进编译器和Objective C运行时来解决。 – Holly 2016-03-30 02:12:25

0

我一头栽进这个最近,假设@synchronized(_dataLock)做什么它应该做的,因为它是这样一个根本的东西毕竟。

我去调查_dataLock对象,在我的设计,我有几个Database对象会尽自己独立锁定所以我只是为Database每个实例创建_dataLock = [[NSNumber numberWithInt:1] retain]
但是[NSNumber numberWithInt:1]返回相同的对象,如同一个指针!

这意味着我认为只有一个Database实例的局部锁定对于Database的所有实例都不是全局锁定。
当然,这不是预期的设计,我相信这是问题的原因。

我会改变

_dataLock = [[NSNumber numberWithInt:1] retain] 

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

“我会改变”让我觉得你不确定这会解决问题。无论如何,它看起来像你还没有真正尝试过。无论如何,我认为一句话是矛盾的:“我认为只有一个数据库实例的本地锁不是对数据库所有实例的全局锁。你确定你想说“我认为本地不是全球性的”吗?这对我来说没有多大意义。也许你的意思是“我认为本地就是全球”?如果是这样,请编辑您的答案并修复它。谢谢! – 2015-11-23 18:10:17