讲讲 iOS 中的死锁

点击下方公众号卡片,关注我,每天分享一个关于 iOS 的新知识

一、什么是死锁

在 Swift 中,当两个线程都在等待对方释放资源时,就会发生deadlock 死锁。这会导致线程都处于永久等待状态,当主线程死锁,应用的表现上就是崩溃,其他子线程死锁可能导致卡死。

一个经典的例子

scss 复制代码
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) 之前同步执行,两个任务互相等待,造成死锁。

在看一个其他线程的死锁

scss 复制代码
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 底部工具栏中暂停应用程序按钮:

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

点击展开后,可以看到当前线程执行到哪行代码,证明当前线程正卡在这里:

解决死锁问题

要解决上述事例中的死锁问题,方法有很多,可以从锁的角度出发,比如,在 runThread1 中先尝试在 2 秒中内尝试获取锁,如果不能获取,则放弃任务执行:

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

如何避免死锁

要避免死锁,可以通过以下方法:

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

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

  • 设置请求锁的超时时间: 如果在指定时间内请求不到锁,该线程可以先释放当前锁

  • 使用递归锁: 递归锁同一个线程可以多次获取,避免多线程竞争同一把锁

按照这些原则编写代码,可以避免或者减少死锁的发生。

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
2501_915106321 小时前
H5 混合应用加密实践,从明文资源到安全 IPA 的多层防护体系
android·安全·ios·小程序·uni-app·iphone·webview
kdniao12 小时前
iOS应用集成物流API接口:架构设计、性能优化与用户体验实践指南
ios·性能优化·ux
2501_916007472 小时前
在 CICD 中实践 Fastlane + Appuploader 命令行,构建可复制的 iOS 自动化发布流程
android·运维·ios·小程序·uni-app·自动化·iphone
2501_915921433 小时前
从 HBuilder 到 App Store,uni-app 与 HBuilder 项目的 iOS 上架流程实战解析
android·ios·小程序·https·uni-app·iphone·webview
Sheffi664 小时前
Swift 与 OC 混编底层交互原理
ios·objective-c·swift
游戏开发爱好者84 小时前
以 uni-app 为核心的 iOS 上架流程实践, 从构建到最终提交的完整路径
android·ios·小程序·https·uni-app·iphone·webview
Sheffi665 小时前
iOS 内存分配机制:Malloc、VM、Dirty Memory
macos·ios·cocoa
游戏开发爱好者85 小时前
构建可落地的 iOS 性能测试体系,从场景拆解到多工具协同的工程化实践
android·ios·小程序·https·uni-app·iphone·webview
东坡肘子5 小时前
挖掘“沉默的专家” -- 肘子的 Swift 周报 #114
人工智能·swiftui·swift
sweet丶14 小时前
理解iOS中Protobuf:一个比JSON更好,但不是替代
ios·性能优化·架构