相信讀者已經看過很多大神們對gcd深入淺出的分析,這也是老生常談的一個多線程的實作方式了,是以我也就不再啰嗦其理論。但是到底有多少方法是我們日常程式設計中常用的?又有多少是你不知道的?今天,我就來例舉一些gcd的方法,絕對讓你看一眼就會正确得使用。
當任務互相依賴,具有明顯的先後順序的時候,使用串行隊列是一個不錯的選擇 建立一個串行隊列:
第一個參數為隊列名,第二個參數為隊列類型,當然,第二個參數如果寫null,建立出來的也是一個串行隊列。然後我們在異步線程來執行這個隊列:
為了能更好的了解,我給每個異步線程都添加了一個log,看一下日志平台的log:
沒錯,他在61497這個編号的線程中做了串行輸出,互相彼此依賴,串行執行
與串行隊列剛好相反,他不會存在任務間的互相依賴。
建立一個并發隊列:
比較2個隊列的建立,我們發現隻有第二個參數從<code>dispatch_queue_serial</code>變成了對應的<code>dispatch_queue_concurrent</code>,其他完全一樣。
用同一段代碼,換一種隊列我們來比較一下效果:
輸出的log:
我們發現,log的輸出在3個不同編号的線程中進行,而且互相不依賴,不阻塞。
這是系統為我們準備的2個隊列:
global queue其實就是系統建立的concurrent diapatch queue
main queue 其實就是系統建立的位于主線程的serial diapatch queue
通常情況我們會把這2個隊列放在一起使用,也是我們最常用的開異步線程-執行異步任務-回主線程的一種方式:
通過上面的代碼我們發現了2個有意思的點:
<code>dispatch_get_global_queue</code>存在優先級,沒錯,他一共有4個優先級:
在指定優先級之後,同一個隊列會按照這個優先級執行,列印的順序為1、2、3、4,當然這不是串行隊列,是以不存在絕對回調先後。
異步主線程
在日常工作中,除了在其他線程傳回主線程的時候需要用這個方法,還有一些時候我們在主線程中直接調用異步主線程,這是利用dispatch_async的特性:block中的任務會放在主線程本次runloop之後傳回。這樣,有些存在先後順序的問題就可以得到解決了。
剛剛我們說了系統的global queue是可以指定優先級的,那我們如何給自己建立的隊列執行優先級呢?這裡我們就可以用到<code>dispatch_set_target_queue</code>這個方法:
我把自己建立的隊列塞到了系統提供的<code>global_queue</code>隊列中,我們可以了解為:我們自己建立的queue其實是位于<code>global_queue</code>中執行,是以改變<code>global_queue</code>的優先級,也就改變了我們自己所建立的queue的優先級。是以我們常用這種方式來管理子隊列。
這個是最常用的,用來延遲執行的gcd方法,因為在主線程中我們不能用sleep來延遲方法的調用,是以用它是最合适的,我們做一個簡單的例子:
輸出的結果:
我們看到他就是在主線程,就是剛好延遲了2秒,當然,我說這個2秒并不是絕對的,為什麼這麼說?還記得我之前在介紹<code>dispatch_async</code>這個特性的時候提到的嗎?他的block中方法的執行會放在主線程runloop之後,是以,如果此時runloop周期較長的時候,可能會有一些時差産生。
當我們需要監聽一個并發隊列中,所有任務都完成了,就可以用到這個group,因為并發隊列你并不知道哪一個是最後執行的,是以以單獨一個任務是無法監聽到這個點的,如果把這些單任務都放到同一個group,那麼,我們就能通過<code>dispatch_group_notify</code>方法知道什麼時候這些任務全部執行完成了。
在例子中,我把3個log分别放在并發隊列中,通過把這個并發隊列任務統一加入group中,group每次runloop的時候都會調用一個方法<code>dispatch_group_wait(group, dispatch_time_now)</code>,用來檢查group中的任務是否已經完成,如果已經完成了,那麼會執行<code>dispatch_group_notify</code>的block,輸出’down’看一下運作結果:
此方法的作用是在并發隊列中,完成在它之前送出到隊列中的任務後打斷,單獨執行其block,并在執行完成之後才能繼續執行在他之後送出到隊列中的任務:
輸出的結果為:
4之後的任務在我線程sleep之後才執行,這其實就起到了一個線程鎖的作用,在多個線程同時操作一個對象的時候,讀可以放在并發進行,當寫的時候,我們就可以用<code>dispatch_barrier_async</code>方法,效果杠杠的。
<code>dispatch_sync</code> 會在目前線程執行隊列,并且阻塞目前線程中之後運作的代碼,是以,同步線程非常有可能導緻死鎖現象,我們這邊就舉一個死鎖的例子,直接在主線程調用以下代碼:
根據fifo(先進先出)的原則,block裡面的代碼應該在主線程此次runloop後執行,但是由于他是同步隊列,所有他之後的代碼會等待其執行完成後才能繼續執行,2者互相等待,是以就出現了死鎖。
我們再舉一個比較特殊的例子:
其列印結果為:
從線程編号中我們發現,同步方法沒有去開新的線程,而是在目前線程中執行隊列,會有人問,上文說dispatch_get_global_queue不是并發隊列,并發隊列不是應該會在開啟多個線程嗎?這個前提是用異步方法。gcd其實是弱化了線程的管理,強化了隊列管理,這使我們了解變得比較形象。
這個方法用于無序查找,在一個數組中,我們能開啟多個線程來查找所需要的值,我這邊也舉個例子:
輸出結果:
通過輸出log,我們發現這個方法雖然會開啟多個線程來周遊這個數組,但是在周遊完成之前會阻塞主線程。
隊列挂起和恢複,這個沒什麼好說的,直接上代碼:
我們甚至可以在不同的線程對這個隊列進行挂起和恢複,因為gcd是對隊列的管理。
我們可以通過設定信号量的大小,來解決并發過多導緻資源吃緊的情況,以單核cpu做并發為例,一個cpu永遠隻能幹一件事情,那如何同時處理多個事件呢,聰明的核心工程師讓cpu幹第一件事情,一定時間後停下來,存取進度,幹第二件事情以此類推,是以如果開啟非常多的線程,單核cpu會變得非常吃力,即使多核cpu,核心數也是有限的,是以合理配置設定線程,變得至關重要,那麼如何發揮多核cpu的性能呢?如果讓一個核心模拟傳很多線程,經常幹一半放下幹另一件事情,那效率也會變低,是以我們要合理安排,将單一任務或者一組相關任務并發至全局隊列中運算或者将多個不相關的任務或者關聯不緊密的任務并發至使用者隊列中運算,是以用好信号量,合理配置設定cpu資源,程式也能得到優化,當日常使用中,信号量也許我們隻起到了一個計數的作用,真的有點大材小用。
這個函數一般是用來做一個真的單例,也是非常常用的,在這裡就舉一個單例的例子吧:
好了,blog說了這麼多關于gcd中的方法,大家是不是覺得這篇blog并沒有什麼高深的理論,本文更傾向于實用,看完這篇blog之後,大家一定對gcd躍躍欲試了吧!
參考文獻:《objective-c進階程式設計 ios與os x多線程》