点击下方公众号卡片,关注我,每天分享一个关于 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()
}
}
仔细观察上述代码,预测将会如何执行?结果是 runThread1
和 runThread2
两个方法中的 print
都不会执行。
原因是在上面的代码中:
-
thread1
先获取lock1
,然后sleep
1 秒后想获取lock2
,但其实这时候lock2
已经被thread2
获取了,需要等待lock2
释放。 -
thread2
先获取lock2
,然后sleep
1 秒后想获取lock1
,但其实这时候lock1
已经被thread1
获取了,需要等待lock1
释放。
最终导致 thread1
和 thread2
互相等待对方的锁释放,这就造成了互相等待的死锁情况。
如何检测死锁
在主线程中如果发生死锁情况通常会导致崩溃,问题很容易被发现,但如果是上述例子中那样,在子线程中发生的死锁,往往比较让人头疼。
当发现程序没有按照预期执行时,即 runThread1
和 runThread2
两个方法中的 print
都没有执行,可以手动点击 Xcode 底部工具栏中暂停应用程序按钮:
然后在 Xcode 左侧线程列表中,找到这两个线程:
点击展开后,可以看到当前线程执行到哪行代码,证明当前线程正卡在这里:
解决死锁问题
要解决上述事例中的死锁问题,方法有很多,可以从锁的角度出发,比如,在 runThread1
中先尝试在 2 秒中内尝试获取锁,如果不能获取,则放弃任务执行:
php
guard lock2.lock(before: Date.now.addingTimeInterval(2)) else {
print("2s 内无法获取锁")
lock1.unlock()
return
}
如何避免死锁
要避免死锁,可以通过以下方法:
-
锁的顺序一致: 所有线程都以相同的顺序请求多个锁
-
线程不持有锁时再请求新的锁: 只有线程释放了当前锁后,才能去请求新的锁
-
设置请求锁的超时时间: 如果在指定时间内请求不到锁,该线程可以先释放当前锁
-
使用递归锁: 递归锁同一个线程可以多次获取,避免多线程竞争同一把锁
按照这些原则编写代码,可以避免或者减少死锁的发生。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!