锁定等待@synchronized [英] Locked up waiting for @synchronized

查看:54
本文介绍了锁定等待@synchronized的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个(罕见的)奇怪的情况,我的 Objective-c iOS 程序被锁定了.当我闯入调试器时,有两个线程,它们都卡在 @synchronized() 上.

I have this (rare) odd case where my objective-c iOS program is locking up. When I break into the debugger, there are two threads and both of them are stuck at a @synchronized().

除非我完全误解了@synchronized,否则我认为这是不可能的,以及命令的全部意义.

Unless I am completely misunderstanding @synchronized, I didn't think that was possible and the whole point of the command.

我有一个主线程和工作线程,它们都需要访问 sqlite 数据库,所以我将访问数据库的代码块封装在 @synchronized(myDatabase) 块中.除了数据库访问之外,这些块中没有其他任何事情发生.

I have a main thread and worker thread that both need access to a sqlite database, so I wrap the chunks of code that are accessing the db in @synchronized(myDatabase) blocks. Not much else happens in these blocks except the db access.

我也在使用 FMDatabase 框架访问 sqlite,我不知道这是否重要.

I'm also using the FMDatabase framework to access sqlite, I don't know if that matters.

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

The myDatabase is a global variable that contains the FMDatabase object. It is created once at the start of the program.

推荐答案

我知道我迟到了,但我发现了 @synchronized 处理的奇怪组合很差,可能是造成您问题的原因.我没有解决办法,除了改代码,一旦你知道是什么原因就可以消除它.

I know I'm late to the party with this, but I've found a strange combination of circumstances that @synchronized handles poorly and is probably responsible for your problem. I don't have a solution to it, besides to change the code to eliminate the cause once you know what it is.

我将在下面使用此代码进行演示.

I will be using this code below to demonstrate.

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

Generally, @synchronized is a recursively-safe lock. As such, calling [self printEight] is ok and will not cause deadlocks. What I've found is an exception to that rule. The following series of events will cause deadlock and is extremely difficult to track down.

  1. 线程 1 进入 -printEight 并获取锁.
  2. 线程 2 进入 -printSomethingElse 并尝试获取锁.该锁由线程 1 持有,因此它会排队等待,直到该锁可用并阻塞.
  3. 线程 1 输入 -getNumberEight 并尝试获取锁.锁已经被持有,其他人在队列中等待下一个获取它,所以线程 1 阻塞.死锁.
  1. Thread 1 enters -printEight and acquires the lock.
  2. Thread 2 enters -printSomethingElse and attempts to acquire the lock. The lock is held by Thread 1, so it is enqueued to wait until the lock is available and blocks.
  3. Thread 1 enter -getNumberEight and attempts to acquire the lock. The lock is held already and someone else is in the queue to get it next, so Thread 1 blocks. Deadlock.

看来,此功能是使用 @synchronized 时希望限制饥饿的意外结果.该锁只有在没有其他线程等待它时才是递归安全的.

It appears that this functionality is an unintended consequence of the desire to bound starvation when using @synchronized. The lock is only recursively safe when no other thread is waiting for it.

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

The next time you hit deadlock in your code, examine the call stacks on each thread to see if either of the deadlocked threads already holds the lock. In the sample code above, by adding long sleeps at Point A, B, and C, the deadlock can be recreated with almost 100% consistency.

我无法再演示之前的问题,但有一个相关的情况仍然会导致问题.它与 dispatch_sync 的动态行为有关.

I'm no longer able to demonstrate the previous issue, but there is a related situation that still causes issues. It has to do with the dynamic behavior of dispatch_sync.

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

In this code, there are two attempts to acquire the lock recursively. The first calls from the main queue into a background queue. The second calls from the background queue into the main queue.

导致行为差异的原因是调度队列和线程之间的区别.第一个示例调用不同的队列,但从不更改线程,因此获取递归互斥锁.第二个在更改队列时更改线程,因此无法获取递归互斥锁.

What causes the difference in behavior is the distinction between dispatch queues and threads. The first example calls onto a different queue, but never changes threads, so the recursive mutex is acquired. The second changes threads when it changes queues, so the recursive mutex cannot be acquired.

强调一下,此功能是设计使然,但对于一些不太了解 GCD 的人来说,它的行为可能出乎意料.

To emphasize, this functionality is by design, but it behavior may be unexpected to some that do not understand GCD as well as they could.

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的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆