点击下方大众号卡片,重视我,每天共享一个关于 iOS 的新知识

一、什么是死锁

在 Swift 中,当两个线程都在等候对方释放资源时,就会产生deadlock 死锁。这会导致线程都处于永久等候状况,当主线程死锁,使用的表现上就是溃散,其他子线程死锁或许导致卡死。

一个经典的比如

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        print(1)
        DispatchQueue.main.sync {
            print(2)
        }
        print(3)
    }
}

判别上述代码将会输出什么内容?这是一个经典的面试题,答案是输出 1,然后程序溃散。

原因是:viewDidLoad 办法在主线程履行,先履行 print(1) 没有问题,然后 DispatchQueue.main.sync 办法会将 print(2) 使命插入到主行列的队尾,那么 print(2) 就需求等候 print(3) 先履行,但 print(2) 又需求在 print(3) 之前同步履行,两个使命相互等候,造成死锁。

在看一个其他线程的死锁

class ViewController: UIViewController {
    let lock1 = NSLock()
    let lock2 = NSLock()
    override func viewDidLoad() {
        super.viewDidLoad()
        let thread1 = DispatchQueue(label: "thread1")
        let thread2 = DispatchQueue(label: "thread2")
        // 分别在两条线程中调用runThread1和runThread2
        thread1.async {
            self.runThread1()
        }
        thread2.async {
            self.runThread2()
        }
        // sleep 让主线程暂停以履行其他线程
        sleep(2)
        print("Main done")
    }
    func runThread1() {
        lock1.lock()
        sleep(1)
        lock2.lock()
        print("Thread 1 done")
        lock1.unlock()
        lock2.unlock()
    }
    func runThread2() {
        lock2.lock()
        sleep(1)
        lock1.lock()
        print("Thread 2 done")
        lock1.unlock()
        lock2.unlock()
    }
}

仔细观察上述代码,预测将会怎么履行?结果是 runThread1runThread2 两个办法中的 print 都不会履行。

原因是在上面的代码中:

  • thread1 先获取 lock1,然后 sleep 1 秒后想获取 lock2,但其实这时候 lock2 现已被 thread2 获取了,需求等候 lock2 释放。

  • thread2 先获取 lock2,然后 sleep 1 秒后想获取 lock1,但其实这时候 lock1 现已被 thread1 获取了,需求等候 lock1 释放。

终究导致 thread1thread2 相互等候对方的锁释放,这就造成了相互等候的死锁情况。

怎么检测死锁

在主线程中假如产生死锁情况通常会导致溃散,问题很容易被发现,但假如是上述比如中那样,在子线程中产生的死锁,往往比较让人头疼。

当发现程序没有依照预期履行时,即 runThread1runThread2 两个办法中的 print 都没有履行,能够手动点击 Xcode 底部工具栏中暂停使用程序按钮:

讲讲 iOS 中的死锁

然后在 Xcode 左边线程列表中,找到这两个线程:

讲讲 iOS 中的死锁

点击展开后,能够看到当时线程履行到哪行代码,证明当时线程正卡在这儿:

讲讲 iOS 中的死锁

处理死锁问题

要处理上述案例中的死锁问题,办法有很多,能够从锁的角度动身,比如,在 runThread1 中先测验在 2 秒中内测验获取锁,假如不能获取,则抛弃使命履行:

guard lock2.lock(before: Date.now.addingTimeInterval(2)) else {
    print("2s 内无法获取锁")
    lock1.unlock()
    return
}

怎么防止死锁

要防止死锁,能够经过以下办法:

  • 锁的顺序一致: 所有线程都以相同的顺序恳求多个锁

  • 线程不持有锁时再恳求新的锁: 只要线程释放了当时锁后,才能去恳求新的锁

  • 设置恳求锁的超时时间: 假如在指定时间内恳求不到锁,该线程能够先释放当时锁

  • 使用递归: 递归锁同一个线程能够多次获取,防止多线程竞争同一把锁

依照这些原则编写代码,能够防止或许削减死锁的产生。

这儿每天共享一个 iOS 的新知识,快来重视我吧

本文同步自微信大众号 “iOS新知”,每天准时共享一个新知识,这儿仅仅同步,想要及时学到就来重视我吧!