柵欄函數(shù)
關(guān)于柵欄函數(shù)楚里,系統(tǒng)提供了兩個(gè)方法
- dispatch_barrier_async
- dispatch_barrier_sync
dispatch_barrier_sync
和dispatch_barrier_async
區(qū)別會(huì)不會(huì)阻塞當(dāng)前的線程,要注意猎贴,柵欄函數(shù)只能控制同一隊(duì)列班缎。
全局并發(fā)隊(duì)列 :dispatch_get_global_queue
會(huì)使柵欄函數(shù)失效
柵欄函數(shù)使用
同步柵欄函數(shù)
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
/* 1.異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
/* 2. 柵欄函數(shù) */
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"3");
});
/* 3. 異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"4");
});
// 4
NSLog(@"5");
這里打印的結(jié)果為
12354
,接下來分析下
- 因?yàn)檫@里是
dispatch_barrier_sync
同步柵欄函數(shù)她渴,阻塞當(dāng)前的線程达址,所以5一定是在3
后面打印 - 柵欄函數(shù)是在同一隊(duì)列的任務(wù),柵欄上方的任務(wù)先執(zhí)行趁耗,當(dāng)上方任務(wù)執(zhí)行完畢再執(zhí)行柵欄內(nèi)部任務(wù)沉唠,最后執(zhí)行柵欄下方任務(wù)
- 所以
1,2
先打印,1,2
的順序不固定对粪。接下來一定是打印3
- 后面打印
5
右冻,是因?yàn)?code>dispatch_async本身存在耗時(shí)操作,4一定在5后面
異步柵欄函數(shù)
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
/* 1.異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2");
});
/* 2. 柵欄函數(shù) */ //
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"3");
});
/* 3. 異步函數(shù) */
dispatch_async(concurrentQueue, ^{
NSLog(@"4");
});
// 4
NSLog(@"5");
這里的打印順序?yàn)?code>51234著拭,接下來分析下
- 觀察打印順序和
同步柵欄函數(shù)
唯一的區(qū)別是5
的打印 - 是因?yàn)楫惒綎艡诤瘮?shù)不會(huì)阻塞當(dāng)前線程纱扭,而
dispatch_async
存在耗時(shí),所以5
先打印儡遮,剩下的順序與同步柵欄函數(shù)一致
柵欄函數(shù)底層分析
通過libdispatch源碼
進(jìn)入函數(shù)dispatch_barrier_sync
->_dispatch_barrier_sync_f
->_dispatch_barrier_sync_f_inline
進(jìn)入_dispatch_barrier_sync_f_inline
_dispatch_sync_f_slow
_dispatch_sync_recurse
-
_dispatch_lane_barrier_sync_invoke_and_complete
這里有這3個(gè)方法我們不知道最終進(jìn)入哪個(gè)乳蛾,我們通過符號(hào)斷點(diǎn)來確認(rèn)
image.png
進(jìn)入_dispatch_sync_f_slow
image.png
繼續(xù)跟蹤流程,并添加_dispatch_sync_invoke_and_complete_recurse
的符號(hào)斷點(diǎn)鄙币,并進(jìn)入
image.png
進(jìn)入_dispatch_sync_complete_recurse
image.png
這里我們發(fā)現(xiàn)是個(gè)do while
循環(huán)肃叶,這里思考下為什么這么寫? - 柵欄函數(shù)起到的的是同步作用十嘿,同一隊(duì)列中因惭,柵欄前的任務(wù)沒有執(zhí)行柵欄函數(shù)是不會(huì)走的
- 所以這里需要
遞歸處理柵欄函數(shù)前面的任務(wù)
- 在
_dispatch_sync_complete_recurse
中的遞歸
中,先判斷barrier
是否存在绩衷,如果存在則需要先把柵欄前的任務(wù)dx_wakeup 全部喚醒
蹦魔。喚醒成功后才會(huì)執(zhí)行_dispatch_lane_non_barrier_complete
先來查看dx_wakeup
,來查看barrier
什么時(shí)候被移出咳燕,dx_wakeup
是通過宏定義的函數(shù)
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
搜索dq_wakeup
由上圖可知
串行隊(duì)列和并行隊(duì)列
都走了_dispatch_lane_wakeup
勿决,而全局并發(fā)隊(duì)列
走了_dispatch_root_queue_wakeup
- 串行、并行隊(duì)列招盲,進(jìn)入
_dispatch_lane_wakeup
image.png
進(jìn)入_dispatch_lane_barrier_complete
image.png - 如果是
串行隊(duì)列
則會(huì)進(jìn)行等待低缩,知道其他任務(wù)執(zhí)行完成,按順序執(zhí)行 - 如果是并行隊(duì)列,調(diào)用
_dispatch_lane_drain_non_barriers
將柵欄前的任務(wù)按照異步的放心執(zhí)行
- 全局并發(fā)隊(duì)列
當(dāng)是全局并發(fā)隊(duì)列的時(shí)候,進(jìn)入_dispatch_root_queue_wakeup
image.png
由上圖可知曹货,全局并發(fā)隊(duì)列并沒有柵欄函數(shù)的相關(guān)處理流程咆繁,這也是柵欄函數(shù)在全局并發(fā)隊(duì)列失效的原因
【問題】為什么全局并發(fā)隊(duì)列中不對(duì)柵欄函數(shù)進(jìn)行處理
【答】因?yàn)槿植l(fā)隊(duì)列除了被我們使用讳推,系統(tǒng)也在使用,如果添加了柵欄函數(shù)么介,會(huì)導(dǎo)致隊(duì)列運(yùn)行的阻塞娜遵,從而影響系統(tǒng)級(jí)的運(yùn)行,所以柵欄函數(shù)也就不適用于全局并發(fā)隊(duì)列壤短。
信號(hào)量(dispatch_semaphore_t)
-
dispatch_semaphore_create
創(chuàng)建一個(gè)Semaphore
设拟,并初始信號(hào)總量 -
dispatch_semaphore_wait
信號(hào)量減1
,當(dāng)信號(hào)量小于0
時(shí),就會(huì)所在線程發(fā)生阻塞久脯,大于等于0
時(shí)纳胧,正常執(zhí)行 -
dispatch_semaphore_signal
信號(hào)量加1
案例
這里我i們的正常理解應(yīng)該是先執(zhí)行
任務(wù)1
,但是這里初始化的信號(hào)總量為0
,且在任務(wù)1中dispatch_semaphore_wait
起到了加鎖作用,所以先去執(zhí)行任務(wù)2
,且發(fā)出了dispatch_semaphore_signal
解鎖信號(hào)帘撰,再去執(zhí)行任務(wù)`
信號(hào)量底層分析
dispatch_semaphore_wait
首先對(duì)信號(hào)量做
減1
操作跑慕,當(dāng)信號(hào)量大于等于0時(shí)直接返回,否則進(jìn)入_dispatch_semaphore_wait_slow
方法對(duì)
timeout
進(jìn)行判斷
- 如果是默認(rèn)的摧找,會(huì)直接跳出
-
DISPATCH_TIME_NOW
核行,會(huì)進(jìn)行一個(gè)超時(shí)處理 -
DISPATCH_TIME_FOREVER
會(huì)進(jìn)入_dispatch_sema4_wait
方法
進(jìn)入_dispatch_sema4_wait
方法
void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
int ret = 0;
do {
ret = sem_wait(sema);
} while (ret == -1 && errno == EINTR);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
}
我們發(fā)現(xiàn)有個(gè)do while
方法,并調(diào)用sem_wait
蹬耘,全局搜索sem_wait
并沒有搜索出
sem_wait
方法的實(shí)現(xiàn)所以
_dispatch_sema4_wait
的do while
就是個(gè)死循環(huán)芝雪,原因就是要讓該任務(wù)一致處于等待狀態(tài)
dispatch_semaphore_signal
通過
os_atomic_inc2o
對(duì)信號(hào)量做+1
操作,如果大于0
直接返回综苔。如果
加過一次后仍小于0
,則會(huì)拋出異常Unbalanced call to dispatch_semaphore_signal()
并調(diào)用_dispatch_semaphore_signal_slow
方法惩系,見下圖:這里會(huì)開啟一個(gè)循環(huán),對(duì)信號(hào)量加一操作如筛,知道滿足條件位置
【總結(jié)】信號(hào)來那個(gè)在實(shí)際開發(fā)中的作用
- 保持線程同步堡牡,將異步執(zhí)行的任務(wù),轉(zhuǎn)換成同步操作
- 保證線程安全杨刨,為線程加鎖(自旋鎖)
調(diào)度組
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í)間
dispatch_group_enter
進(jìn)組
dispatch_group_leave
出組
兩者搭配使用
案例實(shí)現(xiàn)1
我們?cè)诋惒骄€程中加了個(gè)
sleep(2)
,這個(gè)時(shí)候在主線程
打印為空數(shù)組
,但是在我們的dispatch_group_notify
中是能夠打印出數(shù)組的內(nèi)容晤柄。是因?yàn)樵谙嗤M中的任務(wù),都執(zhí)行完畢后會(huì)走dispatch_group_notify
該方法妖胀。【注意】
dispatch_group_enter
和dispatch_group_leave
要成對(duì)出現(xiàn)可免,并且dispatch_group_enter
在執(zhí)行任務(wù)前,dispatch_group_leave
任務(wù)執(zhí)行完成后調(diào)用做粤,否則順序錯(cuò)誤會(huì)報(bào)錯(cuò)
案例實(shí)現(xiàn)2
這里直接將任務(wù)放在
dispatch_group_async
,最終結(jié)果和上述案例相同捉撮,其實(shí)dispatch_group_async
就是底層封裝了dispatch_group_enter 和dispatch_group_leave
調(diào)度組底層分析
dispatch_group_create
調(diào)用
_dispatch_group_create_with_count
并將信號(hào)量默認(rèn)傳0
通過
os_atomic_store2o
進(jìn)行保存
dispatch_group_enter
默認(rèn)信號(hào)量為0 怕品,所以信號(hào)量
減1
,由0
變-1
,old_bits等于-1
dispatch_group_leave
信號(hào)量
加1
,此時(shí)的newState等于0
巾遭,oldState等于-1
#define DISPATCH_GROUP_VALUE_MASK 0x00000000fffffffcULL
#define DISPATCH_GROUP_VALUE_1 DISPATCH_GROUP_VALUE_MASK
old_state & DISPATCH_GROUP_VALUE_MASK
等于0
肉康,即old_value等于0
也就是 old_value 與DISPATCH_GROUP_VALUE_1
不會(huì)相等闯估,最終調(diào)用if中的 return _dispatch_group_wake
,_dispatch_group_wake
也就是去喚醒dispatch_group_notify
dispatch_group_notify
這里判斷
old_state == 0
就去喚醒函數(shù)的執(zhí)行流程吼和,在上一步已經(jīng)分析出old_state = 0
所以這里也就解釋了dispatch_group_enter和dispatch_group_leave
為什么要配合起來使用