死锁 GCD 多线程 Ios - LDSmallCat - 博客园
Ios中GCD死锁困扰很多人,分享一点个人经验,希望可以帮助到更多人.文章有点长,首先第一张图是正确的代码,交代一下基本流程和原理,第二张图是一个最简单的死锁后面是原理分析,第三张图稍加了一点点难度的死锁,后面是原理分析,第四章是正确的代码,后面是原理分析
我在后面又补充了一篇文章来说死锁.<死锁补充>
一.首先来看这段正确的代码:
在touchesbegan中调用test方法,可以看到如下打印
分析一下原理:
1>调用test方法执,行到25行时没有开启线程,在主线程中打印
2>执行到31行时检测到异步函数async,此时async函数直接返回,不会等函数中的block1执行完毕就返回,执行到31行系统有两件事要做,第一跳至42行打印,第二开启一个新的线程1(是子线程而非主线程)执行block1当中的内容.所以看到主线程0,4先打印,打印结束就没有主线程的事情了,接下来子线程登场.()
3>子线程1执行block1的内容:
3.1执行33行打印在(子线程1)打印
3.2执行到35行检测到async异步函数,直接返回,不等待block2执行完毕,程序继续向下执行,即打印39行3子线程打印,同时又创建一个(子线程2)
4>ok到这里我们还需要分析一下,程序运行到这里我们一共经历了几个线程?三个!主线程一个,31行35行分别创建了两个子线程1和2
(怎么看是否创建了新的线程?"一般"来说看函数,是async异步执行还是sync同步执行,为什么要用"一般"?往下看)
那么问题来了1> 33行和39行的打印(即1,3打印)都在一个线程上我们可以理解(看线程地址),但是为什么36行的打印也和这两个在同一个线程呢?因为31行创建了一个子线程打印了33行和39行之后,这个线程的任务结束了,正常来说就要销毁了,没他什么事了.但是我们在35行又需要创建一个线程,创建线程需要时间和空间成本(占用内存)所以在子线程1执行完任务进入线程池要销毁的时候,发现系统还要再创建一个线程,ok那我就不销毁了,你也不用再创建了,所以线程1就被重复利用了,所以他们在一个线程上打印.关于为什么在同一个子线程上打印,我给出的只是一个理解的方式,是不是准确的?我不确定.因为线程很多的情况十分复杂,我们这个事例程序很简单,可以这样理解.所以我前面说的是有问题的,这个程序只经历了两个线程,主线程和同一个子线程
5>ok我们回来在子线程1执行33 39行的打印之后,被系统重复利用,来执行35行block的内容,36行打印结束一个消息循环结束,进入休眠等待下次触摸屏幕.
6>总结:为什么程序的打印结果会是这样的?与两个因素有关1>函数是async异步执行还是sync同步执行2>队列我们创建的是串行队列这个串行队列里面有两个任务block1和block2,串行队列遵循fifo原则:firstinfirstout,就先进先出原则,blokc1在31行加入,先执行,block2在35行加入后执行.block1中有三个任务33行39行打印和block2,且block2在两个打印中间,按照程序自上到下执行…尼玛问题又来了,为什么block2不是在两个打印任务之间执行?而是在打印结束后执行?ok这时就要看函数啦~async这是什么?异步执行~!!要开启新的线程(或是重复利用线程池中已经创建的线程,什么样的线程可以重复利用?这个线程已经执行完它的任务,进入线程池马上销毁的线程才可以重复利用)(开启线程或是获取线程池中重复利用的线程是需要时间成本的)检测到这个函数怎么办?直接返回,不用等这个函数的block执行完就返回.什么意思?执行到35行的时候兵分两路,一路向下执行39行打印,一路从线程池中获取重复利用的线程处理block2的任务.在兵分两路的时候子线程以还没有处理完39行的打印,它的任务没有结束啊!为什么处理block2没有创建新的线程?因为需要时间和控件成本.GCD为我们做了优化,(怎么优化的我们不用操心~~看不到源码这是个迷,不过看到了也不一定看得懂!哈哈所以不创建新的线程,等子线程1执行完他的任务,我们重复利用它!ok说完了,
二.了解了上图,我们再来看下面的代码
我把35行的函数换成了sync同步执行函数,看打印结果,2.3没有打印!这就是传说中的死锁.什么意思?36 ,39行没有打印,程序在等待,卡住了.不能正常向下执行了!
分析一下原因:
1>点击屏幕调用test方法,主线程打印25行42行,主线程不用我们创建.系统自动创建.上面已经提到原理就不再赘述,直接来重点
2>31行代码做了这几件事:1将block1任务添加到队列串行queue中,2>利用async函数开启新的子线程1处理block1任务.
3>当执行到35行代码时做了这几件事情:1>将block2添加到串行队列queue中,2>sync函数不会开启新的线程,只能在当前线程执行任务
4>ok现在我们分析一下是什么情况:1>queue当中有两个任务,block1和block2.2>只有一个线程:子线程1,在31行创建的.queue串行执行任务,要等block1执行完才能执行block2任务.啥意思?31行创建的子线程1要执行完block1才能执行block2.执行到35行block2任务的时候要等待block1执行完,可是程序自上而下执行,尼玛block2不执行完block1就不会执行完!你懂了么?block2所在的是sync函数不能开启新的线程,只能和block1共用一个线程,这个线程在处理block1,现在的问题是没有线程处理block2的任务~!!!所以就出现了传说中的死锁!别尼玛问我为啥主线程不处理block2的任务!我会让你的老板开除你的!还没懂?最后说一遍:程序自上而下执行知道吧?那么block1执行完时不时需要block1里面的所有代码都执行完啊?可是到了35行block2没有开启新的线程,只能和block1共用一个线程,现在这个线程在处理block1的任务,要等block1执行完才能执行block2的任务.所以程序就卡住了.ok还不懂?看上面正确的代码第35行我们呢用的是async函数,它能开启一个全新的线程来处理block2任务,且不用等到block2执行完这个函数就返回了!函数返回啥意思----就是继续向下执行了.ok说完了.别告诉我你还没
三:有了上面的分析相信小伙伴们有了一定的了解,再来分析一下下面的代码是不是死锁?
所在哪里了?
是死锁,锁在64行的代码.
分析一下原因:
1>主线程执行47, 71行打印之后进入休眠状态等待下次点击屏幕,在两个打印之间检测到54行async不等block1执行完继续向下.ok没问题
2>54行将block1加入到queue队列,开async函数开启新的线程执行block1,block1任务代码书讯执行56行打印
3>58行检测到async函数不等block2执行完继续向下执行,62行打印,
4>执行到64行sync函数没有开启新的线程,要和block1共用一个线程,造成死锁
5>分析一下现在什么情况:queue中有3个任务,block1,block2,block3,顺序执行,为什么卡在64行?不是58行?58行开启了一个新的线程来处理block2,所以不是这里.block3没有开启线程要用别人的线程,用谁的呢?你看它在哪个block里.她在block1里面,block1在54行开启了线程正在处理block1,所以现在block2没人处理.就造成死锁.
6>那么为什么block2没有打印呢?有线程处理他的任务啊?这是要看队列queue,三个任务顺序执行,block1执行完了么?没有,所以block2不会打印.ok说完了!有疑问?block2?好吧来看下面代码.我把64行的函数换了.
为什么是这个打印顺序呀?
1>主线程优先处理,所以0,6先打印,
2>为什么135这个顺序打印丫?135在同一个线程中,即block1的线程中,代码顺序执行,所以这个顺序
3>为什么2,4打印不是穿插在1,3,5之间呢?1>因为2打印要开启新的线程,有时间成本,啥意思,就是我开启一个线程需要时间.所以他要晚
2>135在主线程,优先于子线程处理任务,优先级就比子线程高,所以它晚
3>async函数不等待后面block执行完就向下执行,所以它晚,
4>为什么2,4打印没有开启两个线程而是一个线程,因为开启新的线程需要时间和控件成本GCD为我们自动做了优化,重复利用了线程池中执行完本职任务即将销毁的线程.
5>为什么24的打印顺序是这样:1>因为4利用了2的线程,(看线程地址)代码自上而下执行,queue中block2排在block3之前,所以要block2打印完才能打印block3.