單例
說起單例,我們一般使用GCD的dispath_once來創(chuàng)建單例
對于單例,需要知道以下兩個(gè)問題:
- 1.單例為什么只執(zhí)行一次,底層是如何控制的
- 2.單例的block是在什么時(shí)候進(jìn)行調(diào)用
下面我們來探究一下
單例為什么只執(zhí)行一次
再進(jìn)入dispatch_once的源碼前,我們先看下dispatch_once的參數(shù)
- 1.onceToken,這是一個(gè)
靜態(tài)變量,由于不同位置定義的靜態(tài)變量是不同的,所以靜態(tài)變量具有唯一性。 - 2.block回到

我們看到會調(diào)用dispatch_once_f,其中val是外界傳入的onceToken靜態(tài)變量,而func是_dispatch_Block_invoke(block),我們看下dispatch_once_f的底層實(shí)現(xiàn)


通過上面代碼,可以知道底層主要分為以下幾步
- 1.
將val,也就是靜態(tài)變量轉(zhuǎn)換為dispatch_once_gate_t類型變量l - 2.通過
os_atomic_load獲取此時(shí)的任務(wù)的標(biāo)識符v - 3.如果
v等于DLOCK_ONCE_DONE,表示任務(wù)執(zhí)行過了,只接return - 4.如果
任務(wù)執(zhí)行后,加鎖失敗了,則走到_dispatch_once_mark_done_if_quiesced函數(shù),函數(shù)里再次進(jìn)行存儲,將標(biāo)識符置為DLOCK_ONCE_DONE。 - 5.反之,則通過
_dispatch_once_gate_tryenter嘗試進(jìn)入任務(wù),即解鎖,然后執(zhí)行_dispatch_once_callout執(zhí)行block回調(diào) - 6.如果此時(shí)
有任務(wù)正在執(zhí)行,再有任務(wù)進(jìn)來,則通過_dispatch_once_wait函數(shù)讓新來的任務(wù)進(jìn)入無限次等待。
單例block是什么時(shí)候調(diào)用
上面我們知道func就是任務(wù)block,而處理func的方法就是_dispatch_once_callout,前面判斷_dispatch_once_gate_tryenter解鎖,我們看下_dispatch_once_gate_tryenter這個(gè)方法實(shí)現(xiàn)

其源碼主要是通過底層的os_atomic_cmpxchg方法進(jìn)行對比,如果比較沒有問題,則進(jìn)行加鎖,即任務(wù)的標(biāo)識置為DLOCK_ONCE_UNLOCKED。 我們下面看下_dispatch_once_callout方法源碼

上面方法主要分兩步:
- 1._dispatch_client_callout:block回調(diào)執(zhí)行
- 2._dispatch_once_gate_broadcast:進(jìn)行廣播

再看下_dispatch_once_gate_broadcast方法實(shí)現(xiàn)_dispatch_client_callout主要執(zhí)行回調(diào),其中f就是傳入的_dispatch_Block_invoke(block),即異步回調(diào)


進(jìn)入 _dispatch_once_gate_broadcast -> _dispatch_once_mark_done源碼,主要就是
給dgo->dgo_once一個(gè)值,然后將任務(wù)的標(biāo)識符為DLOCK_ONCE_DONE,即解鎖。
單例總結(jié)
上面我們對單例進(jìn)行了探索,解開了上面所提出的問題。下面總結(jié)一下:
- 1.【單例執(zhí)行一次原理】:GCD單例中,有兩個(gè)重要參數(shù),
onceToken和block,其中onceToken是靜態(tài)變量,具有唯一性,在底層被封裝成了dispatch_once_gate_t類型的變量l,l主要是用來獲取底層原子封裝性的關(guān)聯(lián),即變量v,通過v來查詢?nèi)蝿?wù)的狀態(tài),如果此時(shí)v等于DLOCK_ONCE_DONE,說明任務(wù)已經(jīng)處理過一次了,直接return。 - 2.【block調(diào)用時(shí)機(jī)】:如果此時(shí)
任務(wù)沒有執(zhí)行過,則會在底層通過C++函數(shù)的比較,將任務(wù)進(jìn)行加鎖,即任務(wù)狀態(tài)置為DLOCK_ONCE_UNLOCK,目的是為了保證當(dāng)前任務(wù)執(zhí)行的唯一性,防止在其他地方有多次定義。加鎖之后進(jìn)行block回調(diào)函數(shù)的執(zhí)行,執(zhí)行完成后,將當(dāng)前任務(wù)解鎖,將當(dāng)前的任務(wù)狀態(tài)置為DLOCK_ONCE_DONE,在下次進(jìn)來時(shí),就不會在執(zhí)行,會直接返回 - 3.【對多線程的印象】:如果在
當(dāng)前任務(wù)執(zhí)行期間,有其他任務(wù)進(jìn)來,會進(jìn)入無限次等待,原因是當(dāng)前任務(wù)已經(jīng)獲取了鎖,進(jìn)行了加鎖,其他任務(wù)是無法獲取鎖的。

柵欄函數(shù)
GCD中我們有時(shí)候會用到柵欄函數(shù)來確定任務(wù)順序,柵欄任務(wù)主要有兩種
- 1.
同步柵欄函數(shù)dispatch_barrier_sync(在主線程中執(zhí)行):前面的任務(wù)執(zhí)行完畢才會來到這里,但是同步柵欄函數(shù)會堵塞線程,影響后面的任務(wù)執(zhí)行 - 2.
異步柵欄函數(shù)dispatch_barrier_async:前面的任務(wù)執(zhí)行完畢才會來到這里
柵欄函數(shù)最直接的作用就是控制任務(wù)執(zhí)行順序,保證任務(wù)按計(jì)劃順序執(zhí)行。
柵欄函數(shù)有幾下幾點(diǎn)需要注意:
- 1.柵欄函數(shù)
只能控制同一并發(fā)隊(duì)列 - 2.
同步柵欄添加進(jìn)入隊(duì)列的時(shí)候,當(dāng)前線程會被鎖死,直到同步柵欄之前的任務(wù)和同步柵欄任務(wù)本身執(zhí)行完畢時(shí),當(dāng)前線程才會打開然后繼續(xù)執(zhí)行下一句代碼。 - 3.在
使用柵欄函數(shù)時(shí).使用自定義隊(duì)列才有意義,如果用的是串行隊(duì)列或者系統(tǒng)提供的全局并發(fā)隊(duì)列,這個(gè)柵欄函數(shù)的作用等同于一個(gè)同步函數(shù)的作用,沒有任何意義。
異步柵欄函數(shù)

通過打印我們知道
異步柵欄函數(shù)不會阻塞主線程,堵塞的是異步函數(shù)對列。
同步柵欄函數(shù)

同步柵欄函數(shù)會堵塞主線程,也會堵塞當(dāng)前線程。
柵欄函數(shù)總結(jié)
- 1.
異步柵欄函數(shù)阻塞的是隊(duì)列,而且必須是自定義的并發(fā)隊(duì)列,不影響主線程任務(wù)的執(zhí)行。 - 2.
同步柵欄函數(shù)阻塞的是線程,且是主線程,會影響主線程其他任務(wù)的執(zhí)行。
使用場景
柵欄函數(shù)除了用于控制任務(wù)的執(zhí)行順序,還可以用于數(shù)據(jù)安全。

下面我們添加?xùn)艡诤瘮?shù)崩潰原因:
數(shù)據(jù)不斷的retain和release,在數(shù)據(jù)還沒有retain完畢時(shí),已經(jīng)開始了realse,相當(dāng)于對一個(gè)空數(shù)據(jù),進(jìn)行realse。

奔潰原因和上面一樣,原因是
柵欄函數(shù)對系統(tǒng)的全局隊(duì)列也會阻塞,而系統(tǒng)其他地方也會用到全局隊(duì)列,此時(shí)就會崩潰。

除了使用柵欄函數(shù),還可以使用互斥鎖@synchronized (self) {}這樣就沒有任何問題

之所以
使用self,是因?yàn)閟elf的生命周期大于i和mArray,這樣就保證synchronized不會關(guān)聯(lián)一個(gè)被銷毀的對象。但是慎用@synchronized(self),這種方式很粗糙,容易導(dǎo)致死鎖。
柵欄函數(shù)注意問題
- 1.如果
柵欄函數(shù)中使用全局隊(duì)列,運(yùn)行會崩潰,原因是系統(tǒng)也在用全局并發(fā)隊(duì)列,使用柵欄同時(shí)會攔截系統(tǒng)的,所以會崩潰 - 2.如果將
自定義并發(fā)隊(duì)列改為串行隊(duì)列,即serial ,串行隊(duì)列本身就是有序同步此時(shí)加?xùn)艡?/code>,會浪費(fèi)性能。 - 3.
柵欄函數(shù)只會阻塞一次。
異步柵欄函數(shù) 底層分析
進(jìn)入dispatch_barrier_async源碼實(shí)現(xiàn),其底層的實(shí)現(xiàn)與dispatch_async類似,這里就不再做分析了,有興趣的可以自行探索下

同步柵欄函數(shù)底層分析
進(jìn)入dispatch_barrier_sync源碼,實(shí)現(xiàn)如下


下面我們看下_dispatch_barrier_sync_f_inline方法實(shí)現(xiàn)dispatch_barrier_sync調(diào)用_dispatch_barrier_sync_f,而后調(diào)用_dispatch_barrier_sync_f_inline源碼。

方法實(shí)現(xiàn)分以下幾步:
- 1.通過
_dispatch_tid_self獲取線程ID。 - 2.通過
_dispatch_queue_try_acquire_barrier_sync判斷線程狀態(tài)。
下面看下_dispatch_queue_try_acquire_barrier_sync實(shí)現(xiàn)


通過源碼我們發(fā)現(xiàn)進(jìn)入_dispatch_queue_try_acquire_barrier_sync_and_suspend,然后在這里進(jìn)行釋放 回到_dispatch_barrier_sync_f_inline方法,看1791行:_dispatch_sync_recurse方法


通過上面我們知道:
- 1.通過
_dispatch_sync_recurse,遞歸查找柵欄函數(shù)的target。 - 2.通過
_dispatch_introspection_sync_begin對向前信息進(jìn)行處理。

信號量
信號量的作用一般是用來使任務(wù)同步執(zhí)行,類似于互斥鎖,用戶可以根據(jù)需要控制GCD最大并發(fā)數(shù),一般是這樣使用的

dispatch_semaphore_create 創(chuàng)建
該函數(shù)的底層實(shí)現(xiàn)如下,主要是用來初始化信號量,并設(shè)置GCD的最大并發(fā)數(shù),其最大并發(fā)數(shù)必須大于0。

dispatch_semaphore_wait 加鎖

該函數(shù)的源碼實(shí)現(xiàn)看到,其主要作用是對信號量dsema通過os_atomic_dec2o進(jìn)行了--操作,其內(nèi)部是執(zhí)行的C++的atomic_fetch_sub_explicit方法。
- 1.如果
value 大于等于0,表示操作無效,即執(zhí)行成功。 - 2.如果
value 等于LONG_MIN,系統(tǒng)會拋出一個(gè)crash。 - 3.如果
value 小于0,則進(jìn)入長等待。




將具體的值帶入為
os_atomic_dec2o(dsema, dsema_value, acquire);
os_atomic_sub2o(dsema, dsema_value, 1, m)
os_atomic_sub(dsema->dsema_value, 1, m)
_os_atomic_c11_op(dsema->dsema_value, 1, m, sub, -)
_r = atomic_fetch_sub_explicit(dsema->dsema_value, 1),
等價(jià)于 dsema->dsema_value - 1
_dispatch_semaphore_wait_slow

進(jìn)入
_dispatch_semaphore_wait_slow的源碼實(shí)現(xiàn),當(dāng)value小于0時(shí),根據(jù)等待事件timeout做出不同操作。
dispatch_semaphore_signal 解鎖

該函數(shù)的源碼實(shí)現(xiàn)可以知道,其核心也是
通過os_atomic_inc2o函數(shù)對value進(jìn)行了++操作,os_atomic_inc2o內(nèi)部是通過C++的atomic_fetch_add_explicit。
- 1.如果value 大于 0,表示操作無效,即
執(zhí)行成功。 - 2.如果value 等于0,則進(jìn)入
長等待。
其中os_atomic_dec2o的宏定義轉(zhuǎn)換如下




將具體的值帶入:
os_atomic_inc2o(dsema, dsema_value, release);
os_atomic_add2o(dsema, dsema_value, 1, m)
os_atomic_add(&(dsema)->dsema_value, (1), m)
_os_atomic_c11_op((dsema->dsema_value), (1), m, add, +)
_r = atomic_fetch_add_explicit(dsema->dsema_value, 1),
等價(jià)于 dsema->dsema_value + 1
信號量總結(jié)
- 1.
dispatch_semaphore_create主要就是初始化限號量。 - 2.
dispatch_semaphore_wait是對信號量的value進(jìn)行--,即加鎖操作。 - 3.
dispatch_semaphore_signal是對信號量的value進(jìn)行++,即解鎖操作。

調(diào)度組(線程組)
線程組使用
調(diào)度組的最直接作用是控制任務(wù)執(zhí)行順序,常見的方式如下
dispatch_group_create 創(chuàng)建組
dispatch_group_async 進(jìn)組任務(wù)
dispatch_group_notify 進(jìn)組任務(wù)執(zhí)行完畢通知 dispatch_group_wait 進(jìn)組任務(wù)執(zhí)行等待時(shí)間
//進(jìn)組和出組需要是成對使用的,不然會有問題
dispatch_group_enter 進(jìn)組
dispatch_group_leave 出組
我們看下如何使用 
將dispatch_group_notify移到最前面




通過上面三圖我們可以知道,
dispatch_group_notify前移會導(dǎo)致調(diào)度組失效,第三圖和第四圖可以知道,dispatch_group_enter可以單獨(dú)存在,而dispatch_group_leave必須和dispatch_group_enter成對出現(xiàn),否則報(bào)錯(cuò),報(bào)錯(cuò)有延遲是因?yàn)閍sync是并發(fā),會有延遲。
多加一個(gè)dispatch_group_enter

此時(shí)
不會執(zhí)行notify,原因是少了一個(gè)leave,會讓notify一直等待。
底層源碼
dispatch_group_create 創(chuàng)建組
作用:創(chuàng)建group,并設(shè)置屬性,此時(shí)的group的value為0。
查看dispatch_group_create源碼


上面方法執(zhí)行:
dispatch_group_create->_dispatch_group_create_with_count,其中_dispatch_group_create_with_count是對group對象進(jìn)行賦值,并返回group對象,其中n值為0。
dispatch_group_enter 進(jìn)組
看下dispatch_group_enter
通過os_atomic_sub_orig2o對dg->dg.bits 作 --操作,對數(shù)值進(jìn)行處理
dispatch_group_leave 出組
看下dispatch_group_leave源碼
源碼進(jìn)行如下操作
- 1.-1到0,即++操作
- 2.根據(jù)狀態(tài),do-while循環(huán),喚醒執(zhí)行block任務(wù)
- 3.如果0 + 1 = 1,enter-leave不平衡,即leave多次調(diào)用,會崩潰

執(zhí)行過程:
- 1.do-while循環(huán)進(jìn)行
異步命中 - 2.
_dispatch_continuation_async執(zhí)行任務(wù) - 3.
_dispatch_wake_by_address開始地址釋放 - 4.
_dispatch_release_n引用釋放

這步與
異步函數(shù)的block回調(diào)執(zhí)行時(shí)一致的,不過多解釋
dispatch_group_notify 通知
查看dispatch_group_notify源碼實(shí)現(xiàn)

通過上面我們知道:
如果old_state等于0,就可以進(jìn)行釋放了,除了leave可以通過_dispatch_group_wake喚醒,其中dispatch_group_notify也可以喚醒的。
其中os_mpsc_push_update_tail是宏定義,用于獲取dg的狀態(tài)碼。

dispatch_group_async
查看dispatch_group_async源碼
可以看到dispatch_group_async方法主要做了兩件事:
- 1.
包裝任務(wù) - 2.
異步處理任務(wù)

方法主要封裝了
dispatch_group_enter進(jìn)組操作,之后調(diào)用_dispatch_continuation_async方法,這個(gè)方法在執(zhí)行l(wèi)eave中的_dispatch_group_wake方法里也調(diào)用了。都是進(jìn)行常規(guī)的異步函數(shù)底層操作。
猜想:上面我們知道enter和leave是成對出現(xiàn),所以block執(zhí)行之后可能隱性的執(zhí)行l(wèi)eave,通過斷點(diǎn)調(diào)試,打印堆棧信息

通過堆棧信息,我們看到執(zhí)行_dispatch_client_callout后執(zhí)行銷毀方法_dispatch_call_block_and_release。我們看下_dispatch_client_callout源碼

完美印證dispatch_group_async底層調(diào)用了enter-leave
調(diào)度組總結(jié)
- 1.
enter-leave只要成對出現(xiàn)就可以,不分前后,距離(同一作用域) - 2.
dispatch_group_enter在底層是通過C++函數(shù),對group的value執(zhí)行--操作(即0 -> -1) - 3.
dispatch_group_leave在底層是通過C++函數(shù),對group的value進(jìn)行++操作(即-1 -> 0) - 4.
dispatch_group_notify在底層主要是判斷group的state是否等于0,當(dāng)等于0時(shí),就通知喚醒 - 5.
block任務(wù)的喚醒,可以通過dispatch_group_leave,也可以通過dispatch_group_notify - 6.
dispatch_group_async其底層的調(diào)用了enter和leave

dispatch_source
dispatch_source 定義
定義:dispatch_source是基礎(chǔ)數(shù)據(jù)類型,用于協(xié)調(diào)特定底層系統(tǒng)事件的處理,其CPU負(fù)荷較小,占用很少資源,具有聯(lián)結(jié)優(yōu)勢。
dispatch_source替代了異步回調(diào)函數(shù),來處理系統(tǒng)相關(guān)的事件,當(dāng)配置一個(gè)dispatch時(shí),你需要指定監(jiān)測的事件、dispatch queue、以及處理事件的代碼(block或函數(shù))。當(dāng)事件發(fā)生時(shí),dispatch source會提交你的block或函數(shù)到指定的queue去執(zhí)行。
使用 Dispatch Source 而不使用 dispatch_async 的唯一原因就是利用聯(lián)結(jié)的優(yōu)勢。
dispatch_source流程
在任一線程上調(diào)用它的一個(gè)函數(shù)dispatch_source_merge_data后,會執(zhí)行Dispatch Source事先定義好的句柄(可以把句柄簡單理解為一個(gè)block),這個(gè)過程叫Custom event,用戶事件是dispatch source支持處理的一種事件。
簡單來說就是:事件是由你調(diào)用 dispatch_source_merge_data 函數(shù)來向自己發(fā)出的信號。
句柄:指向指針的指針,它指向的局勢一個(gè)類或者結(jié)構(gòu),它和系統(tǒng)有密切的關(guān)系,這當(dāng)中還有通用句柄,就是HANDLE。它有一下幾類
- 1.實(shí)例句柄 HINSTANCE
- 2.位圖句柄 HBITMAP
- 3.設(shè)備表句柄 HDC
- 4.圖標(biāo)句柄 HICON
使用
創(chuàng)建dispatch

- 1.
type:dispatch源可處理的事件 - 2.
handle:理解為句柄、索引或id,假如要監(jiān)聽進(jìn)程,需要傳入進(jìn)程的ID - 3.
mask:理解為描述,提供更詳細(xì)的描述,讓它知道具體要監(jiān)聽什么 - 4.
queue:自定義源需要的一個(gè)隊(duì)列,用來處理所有的響應(yīng)句柄
Dispatch Source 種類
其中type的類型有一下幾種:
| type(種類) | 說明 |
|---|---|
| DISPATCH_SOURCE_TYPE_DATA_ADD | 自定義的事件,變量增加 |
| DISPATCH_SOURCE_TYPE_DATA_OR | 自定義的事件,變量OR |
| DISPATCH_SOURCE_TYPE_MACH_SEND | MACH端口發(fā)送 |
| DISPATCH_SOURCE_TYPE_MACH_RECV | MACH端口接收 |
| DISPATCH_SOURCE_TYPE_MEMORYPRESSURE | 內(nèi)存壓力 (注:iOS8后可用) |
| DISPATCH_SOURCE_TYPE_PROC | 進(jìn)程監(jiān)聽,如進(jìn)程的退出、創(chuàng)建一個(gè)或更多的子線程、進(jìn)程收到UNIX信號 |
| DISPATCH_SOURCE_TYPE_READ | IO操作,如對文件的操作、socket操作的讀響應(yīng) |
| DISPATCH_SOURCE_TYPE_SIGNAL | 接收到UNIX信號時(shí)響應(yīng) |
| DISPATCH_SOURCE_TYPE_TIMER | 定時(shí)器 |
| DISPATCH_SOURCE_TYPE_VNODE | 文件狀態(tài)監(jiān)聽,文件被刪除、移動、重命名 |
| DISPATCH_SOURCE_TYPE_WRITE | IO操作,如對文件的操作、socket操作的寫響應(yīng) |
上面說了不少類型,我們需要注意兩個(gè)類型:
- 1.
DISPATCH_SOURCE_TYPE_DATA_ADD:當(dāng)同一時(shí)間,一個(gè)事件的的觸發(fā)頻率很高,那么Dispatch Source會將這些響應(yīng)以ADD的方式進(jìn)行累積,然后等系統(tǒng)空閑時(shí)最終處理,如果觸發(fā)頻率比較零散,那么Dispatch Source會將這些事件分別響應(yīng)。 - 2.
DISPATCH_SOURCE_TYPE_DATA_OR:是自定義的事件,但是它是以OR的方式進(jìn)行累積。
常用函數(shù)
//掛起隊(duì)列
dispatch_suspend(queue)
//分派源創(chuàng)建時(shí)默認(rèn)處于暫停狀態(tài),在分派源分派處理程序之前必須先恢復(fù)
dispatch_resume(source)
//向分派源發(fā)送事件,需要注意的是,不可以傳遞0值(事件不會被觸發(fā)),同樣也不可以傳遞負(fù)數(shù)。
dispatch_source_merge_data
//設(shè)置響應(yīng)分派源事件的block,在分派源指定的隊(duì)列上運(yùn)行
dispatch_source_set_event_handler
//得到分派源的數(shù)據(jù)
dispatch_source_get_data
//得到dispatch源創(chuàng)建,即調(diào)用dispatch_source_create的第二個(gè)參數(shù)
uintptr_t dispatch_source_get_handle(dispatch_source_t source);
//得到dispatch源創(chuàng)建,即調(diào)用dispatch_source_create的第三個(gè)參數(shù)
unsigned long dispatch_source_get_mask(dispatch_source_t source);
//取消dispatch源的事件處理--即不再調(diào)用block。如果調(diào)用dispatch_suspend只是暫停dispatch源。
void dispatch_source_cancel(dispatch_source_t source);
//檢測是否dispatch源被取消,如果返回非0值則表明dispatch源已經(jīng)被取消
long dispatch_source_testcancel(dispatch_source_t source);
//dispatch源取消時(shí)調(diào)用的block,一般用于關(guān)閉文件或socket等,釋放相關(guān)資源
void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler);
//可用于設(shè)置dispatch源啟動時(shí)調(diào)用block,調(diào)用完成后即釋放這個(gè)block。也可在dispatch源運(yùn)行當(dāng)中隨時(shí)調(diào)用這個(gè)函數(shù)。
void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler);
使用場景
經(jīng)常用于驗(yàn)證碼倒計(jì)時(shí),因?yàn)閐ispatch_source不依賴于Runloop,而是直接和底層內(nèi)核交互,準(zhǔn)確性更高。

寫到最后
文章我們分析了單例,柵欄函數(shù),信號量,調(diào)度組,以及dispatch_source,主要對單例,柵欄函數(shù),信號量,調(diào)度組的實(shí)現(xiàn)以及查看了其實(shí)現(xiàn)的底層原理。線程的源碼比較難理解,有興趣的可以去官方下載源碼,自己操作理解一下。內(nèi)容比較多,有些地方?jīng)]有詳細(xì)的去說明,有不嚴(yán)謹(jǐn)?shù)牡胤较M魑恢赋?最近分析鎖的底層實(shí)現(xiàn),有時(shí)間會寫出來