iOS gcd dispatch使用注意,dispatch_syn可能产生的死锁
我们在使用dispatch_sync 时可能会出现死锁,看下面的例子:
import UIKit class ViewController: UIViewController { var serialQueue:dispatch_queue_t! var currentQueue:dispatch_queue_t! override func viewDidLoad() { super.viewDidLoad() serialQueue = dispatch_queue_create("com.kings.dboperation", DISPATCH_QUEUE_SERIAL) currentQueue = dispatch_queue_create("com.kings.dboperation", DISPATCH_QUEUE_CONCURRENT) printCurrentThread("main") dispatch_async(serialQueue, {()->Void in sleep(3) self.printCurrentThread("111111111") //阻塞当前操作,并向serialQueue发出block,等待serialQueue执行完毕这个block,才会解除阻塞,也就是要求提交的block先于外部block完成。
//但是我们用的是serialQueue,这个queue要求先加入的block必须先完成,也就是要求外部block要先于内部block完成。
//这就产生了冲突。导致内部的block无法执行,代码锁死在了这里。
//这里可以看出,dispatch_sync即使在新线程中执行也会由于不小心而产生死锁。
dispatch_sync(self.serialQueue, {()->Void in self.printCurrentThread("22222222222") }) self.printCurrentThread("444444") }) } func printCurrentThread(prefix:String){ println(prefix + " current thread is \(NSThread.currentThread())") } }
输出如下
main current thread is <NSThread: 0x7bf40950>{number = 1, name = main} 111111111 current thread is <NSThread: 0x7c0fa170>{number = 2, name = (null)}
程序没有输出2222 和4444的log。详细的解释请看代码中的注视部分。
我们继续试验,换下queue的类型,更改代码如下
dispatch_async(currentQueue, {()->Void in sleep(3) self.printCurrentThread("111111111") dispatch_sync(self.currentQueue, {()->Void in self.printCurrentThread("22222222222") }) self.printCurrentThread("444444") })
下面是输出结果
main current thread is <NSThread: 0x7b7596b0>{number = 1, name = main} 111111111 current thread is <NSThread: 0x7c0298b0>{number = 2, name = (null)} 22222222222 current thread is <NSThread: 0x7c0298b0>{number = 2, name = (null)} 444444 current thread is <NSThread: 0x7c0298b0>{number = 2, name = (null)}
我们看到,死锁消除了,主要是因为current queue 没有“先放入的block必须先结束” 这个约束!
我们可以看出,在任何时刻都不能在一个向serial queue提交的block中再次利用 dispath_sync向这个serial queue提交block,不然就会产生死锁!
再看把源代码相应部分替换为下面这2段代码,它们都在主线程中执行,第一段,死锁代码,
dispatch_sync(serialQueue, {()->Void in sleep(3) self.printCurrentThread("111111111")
//参考我们的上面的结论,这里没有向serialQueue继续提交,而是使用了main queue,竟然也死锁了!
dispatch_sync(dispatch_get_main_queue(), {()->Void in self.printCurrentThread("22222222222") }) self.printCurrentThread("444444") })
第二段,不死锁代码
dispatch_async(serialQueue, {()->Void in sleep(3) self.printCurrentThread("111111111") dispatch_sync(dispatch_get_main_queue(), {()->Void in self.printCurrentThread("22222222222") }) self.printCurrentThread("444444") })
由这2段代码的不同效果,我又尝试了以下代码
override func viewDidLoad() { super.viewDidLoad()
dispatch_sync(dispatch_get_main_queue(), {()->Void in self.printCurrentThread("22222222222") }) }
结果是死锁。
我觉得可以这样理解,viewDidLoad这样的运行在主线程的函数,也是通过GCD机制运行的。viewDidLoad本身就是一个通过sync方法运行在main queue里的代码块,在这个代码块里在用dispatch_sync发送block到同一个queue(这里是main queue),就会产生死锁!上面第二段不死锁的原因就是dispatch_async可以让viewDidload这个代码块不被阻塞,也就不会导致和内部的
dispatch_sync(dispatch_get_main_queue(), {()->Void in self.printCurrentThread("22222222222") })
这段代码块产生资源竞争了!
问:dispatch_async 一定会建立新线程运行代码,一定不会在main thread中执行吗?
答:目前的测试结果看是这样的。
主线程中调用 + dispatch_async + Serial Queue 在新线程中执行
主线程中调用 + dispatch_async + Cuncurrent Queue 在新线程中执行
问:dispatch_sync 会建立新线程吗?
答:从我们上面的测试看,dispatch_sync的代码块就执行在dispatch_sync调用时的线程里,具体结论还需要大量测试。
主线程中调用 + dispatch_sync + Serial Queue :在主线程中执行
主线程中调用 + dispatch_sync + Cuncurrent Queue 在主线程中执行
Serial Queue 保证的是加入到其中的代码块按顺序执行,并不指定block运行的具体线程。
另外async 和 sync都是针对是否阻塞当前代码块来说的,对block加入queue后的执行没有任何影响。