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原則:first in first out,就先進先出原則,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.