這是我參與8月更文挑戰的第7天,活動詳情查看:8月更文挑戰數組
柵欄函數最直接做用是控制任務的執行順序產生同步的效果。安全
dispatch_barrier_async
:前面的任務執行完畢纔會來到這裏dispatch_barrier_sync
:做用相同,可是會阻塞線程,影響後面函數的執行。- (void)demo1 {
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// 這裏是能夠的額!
/* 1.異步函數 */
dispatch_async(concurrentQueue, ^{
NSLog(@"123");
});
/* 2.異步函數 */
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"456");
});
/* 3. 柵欄函數 */ // - dispatch_barrier_sync
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"----%@-----",[NSThread currentThread]);
});
/* 4. 異步函數 */
dispatch_async(concurrentQueue, ^{
NSLog(@"加載那麼多,喘口氣!!!");
});
// 5
NSLog(@"**********起來幹!!");
}
複製代碼
這裏使用了異步併發隊列,在異步併發的時候使用柵欄函數,前面的任務執行完畢纔會來到這裏,可是不會阻塞後面任務的執行,因此步驟3柵欄函數必然在步驟一、2以後執行。步驟一、2無序,步驟四、5無序。 注意:若是咱們把這個併發隊列換成全局併發隊列呢?markdown
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
複製代碼
咱們發現此時柵欄函數並無效果了,也就是說在併發隊列中的柵欄函數在全局併發隊列中失效了,那麼爲何呢?咱們此時照例須要上一份dispatch的源碼來一窺究竟多線程
dispatch_barrier_sync
void dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work) {
uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
if (unlikely(_dispatch_block_has_private_data(work))) {
return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
}
_dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
複製代碼
這個代碼同樣就跟以前介紹的同步函數的很像,咱們定位下調用鏈 dispatch_barrier_sync
-> _dispatch_barrier_sync_f_inline
經過符號斷點咱們定位到了_dispatch_sync_f_slow
-> _dispatch_sync_invoke_and_complete_recurse
-> _dispatch_sync_complete_recurse
併發
static void
_dispatch_sync_complete_recurse(dispatch_queue_t dq, dispatch_queue_t stop_dq,
uintptr_t dc_flags)
{
bool barrier = (dc_flags & DC_FLAG_BARRIER);
do {
if (dq == stop_dq) return;
// 是否存在barrier 存在的話 前面的隊列所有執行
if (barrier) {
dx_wakeup(dq, 0, DISPATCH_WAKEUP_BARRIER_COMPLETE);
} else {
// 不存在 執行普通的同步函數
_dispatch_lane_non_barrier_complete(upcast(dq)._dl, 0);
}
dq = dq->do_targetq;
barrier = (dq->dq_width == 1);
} while (unlikely(dq->do_targetq));
}
複製代碼
存在柵欄函數的話走dx_wakeup
less
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
複製代碼
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
.do_type = DISPATCH_QUEUE_CONCURRENT_TYPE,
.do_dispose = _dispatch_lane_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_lane_invoke,
.dq_activate = _dispatch_lane_activate,
.dq_wakeup = _dispatch_lane_wakeup,
.dq_push = _dispatch_lane_concurrent_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
.do_type = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
.do_dispose = _dispatch_object_no_dispose,
.do_debug = _dispatch_queue_debug,
.do_invoke = _dispatch_object_no_invoke,
.dq_activate = _dispatch_queue_no_activate,
.dq_wakeup = _dispatch_root_queue_wakeup,
.dq_push = _dispatch_root_queue_push,
);
複製代碼
本身建立的併發隊列的話=_dispatch_lane_wakeup
, 全局併發隊列的話=_dispatch_root_queue_wakeup
異步
queue_concurrent
VS queue_global
void
_dispatch_lane_wakeup(dispatch_lane_class_t dqu, dispatch_qos_t qos,
dispatch_wakeup_flags_t flags)
{
dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;
if (unlikely(flags & DISPATCH_WAKEUP_BARRIER_COMPLETE)) {
return _dispatch_lane_barrier_complete(dqu, qos, flags);
}
if (_dispatch_queue_class_probe(dqu)) {
target = DISPATCH_QUEUE_WAKEUP_TARGET;
}
return _dispatch_queue_wakeup(dqu, qos, flags, target);
}
複製代碼
void
_dispatch_root_queue_wakeup(dispatch_queue_global_t dq,
DISPATCH_UNUSED dispatch_qos_t qos, dispatch_wakeup_flags_t flags)
{
if (!(flags & DISPATCH_WAKEUP_BLOCK_WAIT)) {
DISPATCH_INTERNAL_CRASH(dq->dq_priority,
"Don't try to wake up or override a root queue");
}
if (flags & DISPATCH_WAKEUP_CONSUME_2) {
return _dispatch_release_2_tailcall(dq);
}
}
複製代碼
從源碼的地方咱們也能夠明顯的看出來不一樣。在全局併發隊列中並無判斷跟柵欄函數有關的地方,而本身建立的併發隊列則有對柵欄函數的判斷_dispatch_lane_barrier_complete
async
static void
_dispatch_lane_barrier_complete(dispatch_lane_class_t dqu, dispatch_qos_t qos,
dispatch_wakeup_flags_t flags)
{
dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;
dispatch_lane_t dq = dqu._dl;
if (dq->dq_items_tail && !DISPATCH_QUEUE_IS_SUSPENDED(dq)) {
struct dispatch_object_s *dc = _dispatch_queue_get_head(dq);
// 同步函數
if (likely(dq->dq_width == 1 || _dispatch_object_is_barrier(dc))) {
if (_dispatch_object_is_waiter(dc)) {
return _dispatch_lane_drain_barrier_waiter(dq, dc, flags, 0);
}
} else if (dq->dq_width > 1 && !_dispatch_object_is_barrier(dc)) {
return _dispatch_lane_drain_non_barriers(dq, dc, flags);
}
// ...
}
//...
return _dispatch_lane_class_barrier_complete(dq, qos, flags, target, owned);
}
複製代碼
若是是同步隊列就會等待不然就進入到了完成_dispatch_lane_class_barrier_complete
,也就是說要保證前面的全部任務都執行完成。ide
由此也驗證了上面的結論:全局併發隊列不處理柵欄函數相關,因此柵欄函數在全局併發隊列中無用。這樣設計的緣由是,系統級別的也會調用全局併發隊列,而柵欄函數本質是卡住了當前的線程,這樣影響效率。柵欄函數必需要在同一個隊列中使用,好比使用AFN的時候咱們並不能拿到AFN當前的隊列,因此這個柵欄函數平時使用的場景並很少,而咱們使用的最多的是調度組 函數
dispatch_semaphore_t
dispatch_semaphore_create
建立信號量,裏面的數字是表示最大併發數 dispatch_semaphore_wait
信號量等待 -1 dispatch_semaphore_signal
信號量釋放 +1
-(void)test {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
//任務1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
NSLog(@"執行任務1");
NSLog(@"任務1完成");
});
//任務2
dispatch_async(queue, ^{
sleep(2);
NSLog(@"執行任務2");
NSLog(@"任務2完成");
dispatch_semaphore_signal(sem); // 發信號 +1
});
//任務3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
sleep(2);
NSLog(@"執行任務3");
NSLog(@"任務3完成");
dispatch_semaphore_signal(sem);
});
}
複製代碼
任務3不會執行,永遠等待下去了。
dispatch_semaphore_create
* @param value
* The starting value for the semaphore. Passing a value less than zero will
* cause NULL to be returned.
複製代碼
信號量的起始值。傳遞一個小於零的值將致使返回NULL。
dispatch_semaphore_signal
intptr_t dispatch_semaphore_signal(dispatch_semaphore_t dsema) //0 {
long value = os_atomic_inc2o(dsema, dsema_value, release); // +=1 value = 1
if (likely(value > 0)) {
return 0; //直接返回0
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
複製代碼
dispatch_semaphore_wait
intptr_t dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) {
long value = os_atomic_dec2o(dsema, dsema_value, acquire); //-=1 0-1 = -1
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout); // 走這裏
}
複製代碼
上面的例子中,建立的信號量的最大併發數是0,進入到wait
這裏,0-1 = -1
直接到了_dispatch_semaphore_wait_slow
這個函數
static intptr_t
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
dispatch_time_t timeout) // 參數1:0 參數2:FOREVER
{
switch (timeout) {
default:
if (!_dispatch_sema4_timedwait(&dsema->dsema_sema, timeout)) {
break;
}
// ...
case DISPATCH_TIME_FOREVER:
_dispatch_sema4_wait(&dsema->dsema_sema);
break;
}
return 0;
}
複製代碼
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);
}
複製代碼
sem_wait
底層Pthread封裝的內核代碼,咱們只要關注這裏的do while
循環,本質就是do while
死循環等待信號量變爲正。
dispatch_group
最直接的做⽤:控制任務執⾏順序
dispatch_group_create
:建立組dispatch_group_async
: 進組任務dispatch_group_notify
: 進組任務執⾏完畢通知dispatch_group_wait
: 進組任務執⾏等待時間dispatch_group_enter
: 進組dispatch_group_leave
:出組方案一: dispatch_group_async
使用:
- (void)groupDemo{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
});
dispatch_group_async(group, queue, ^{
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
}
複製代碼
方案二: enter
和leave
搭配
- (void)groupDemo{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});
}
複製代碼
上面兩個效果同樣。 爲何dispatch_group_async
= dispatch_group_enter
+ dispatch_group_leave
dispatch_group_create
dispatch_group_t dispatch_group_create(void) {
return _dispatch_group_create_with_count(0);
}
複製代碼
_dispatch_group_create_with_count
函數跟信號量那個挺相似的
dispatch_group_enter
進入到源碼裏面發現這個是個--
操做
dispatch_group_leave
void dispatch_group_leave(dispatch_group_t dg) {
uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
DISPATCH_GROUP_VALUE_INTERVAL, release); //++ -1-> 0
uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);// -1 & 極大值
// old_value == DISPATCH_GROUP_VALUE_MASK
// 因此這一句的判斷就是當 old_value = -1時
if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
old_state += DISPATCH_GROUP_VALUE_INTERVAL;
do {
new_state = old_state;
if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
} else {
new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
}
if (old_state == new_state) break;
} while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
old_state, new_state, &old_state, relaxed)));
return _dispatch_group_wake(dg, old_state, true);
}
if (unlikely(old_value == 0)) {
DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
"Unbalanced call to dispatch_group_leave()");
}
}
複製代碼
由後面的註釋分析能夠知道,當dg = -1
的時候,回來到一個do while
的循環直到喚醒_dispatch_group_wake
,這裏喚醒的是dispatch_group_notify
。回到上面的分析,咱們先進組enter
也就是先--
,此時dg=-1
, 在出組leave
函數來到do while
循環,函數塊走完以後,喚醒notify
dispatch_group_notify
static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dsn)
{
//...
if ((uint32_t)old_state == 0) {
os_atomic_rmw_loop_give_up({
return _dispatch_group_wake(dg, new_state, false);
});
}
}
複製代碼
這裏能夠看到當old_state == 0
,_dispatch_group_wake
開啓正常的異步或者同步函數也就是block的call out流程。若是在異步的時候,先執行到了notify,那麼把此時的block跟當前的組綁定,等到leave出組的通知的時候,_dispatch_group_wake(dg, old_state, true)
。這也就是爲何有兩處都調用了這個方法,主要的目的是爲了解決異步加載的時序問題,是否是設計的很是nice!
dispatch_group_async
= dispatch_group_enter
+ dispatch_group_leave
由上面的例子咱們知道這兩個是等效的,那麼dispatch_group_async
是怎麼封裝進組和出組的實現呢
void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq, dispatch_block_t db) {
dispatch_continuation_t dc = _dispatch_continuation_alloc();
uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
dispatch_qos_t qos;
qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
_dispatch_continuation_group_async(dg, dq, dc, qos);
}
複製代碼
static inline void
_dispatch_continuation_group_async(dispatch_group_t dg, dispatch_queue_t dq,
dispatch_continuation_t dc, dispatch_qos_t qos)
{
dispatch_group_enter(dg);
dc->dc_data = dg;
_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
複製代碼
明顯能夠看到在函數_dispatch_continuation_group_async
有個進組的操做dispatch_group_enter(dg)
,_dispatch_continuation_async
-> dx_push
-> _dispatch_root_queue_push
-> _dispatch_root_queue_push_inline
-> _dispatch_root_queue_poke
-> _dispatch_root_queue_poke_slow
-> _dispatch_root_queues_init
-> _dispatch_root_queues_init_once
-> _dispatch_worker_thread2
-> _dispatch_root_queue_drain
-> _dispatch_continuation_pop_inline
-> _dispatch_continuation_invoke_inline
這些流程以前都來過,就很少說
if (unlikely(dc_flags & DC_FLAG_GROUP_ASYNC)) {
_dispatch_continuation_with_group_invoke(dc);
} else {
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
_dispatch_trace_item_complete(dc);
}
複製代碼
_dispatch_continuation_with_group_invoke
static inline void
_dispatch_continuation_with_group_invoke(dispatch_continuation_t dc)
{
struct dispatch_object_s *dou = dc->dc_data;
unsigned long type = dx_type(dou);
if (type == DISPATCH_GROUP_TYPE) {
_dispatch_client_callout(dc->dc_ctxt, dc->dc_func);
_dispatch_trace_item_complete(dc);
dispatch_group_leave((dispatch_group_t)dou);
} else {
DISPATCH_INTERNAL_CRASH(dx_type(dou), "Unexpected object type");
}
}
複製代碼
在_dispatch_client_callout
以後看到了出組的代碼dispatch_group_leave
,咱們想一想也是,在當前的隊列的任務執行完成以後才能調用出組來通知。
dispatch_source
GCD
和runLoop
實際上是同等級,沒有所謂的歸屬關係,dispatch_source
本質上就是經過條件來控制block
的執行,它的CPU負荷⾮常⼩,儘可能不佔⽤資源。在任⼀線程上調⽤它的的⼀個函數dispatch_source_merge_data
後,會執⾏dispatch_source
事先定義好的句柄(能夠把句柄簡單理解爲⼀個block)這個過程叫Customevent⽤戶事件。
dispatch_source_create
:建立源dispatch_source_set_event_handler
:設置源事件回調dispatch_source_merge_data
:源事件設置數據dispatch_source_get_data
:獲取源事件數據dispatch_resume
:繼續dispatch_suspend
:掛起使用方法比較簡單
-(void)demo {
// 1.建立隊列
self.queue = dispatch_queue_create("hb.com", NULL);
// 2.建立源
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// 3.源事件回調
dispatch_source_set_event_handler(self.source, ^{
NSLog(@"%@",[NSThread currentThread]);
NSUInteger value = dispatch_source_get_data(self.source);
self.totalComplete += value;
NSLog(@"進度: %.2f",self.totalComplete/100.0);
self.progressView.progress = self.totalComplete/100.0;
});
self.isRunning = YES;
dispatch_resume(self.source);
}
// 4.在使用的地方dispatch_source_merge_data修改源數據
// 5.dispatch_resume 繼續
複製代碼
而且這裏不受runLoop
的影響是一個workLoop
,本質是一個pthread
下層封裝。
在多線程中操做同一個數組並不安全,由於會出現同時寫入的狀況,也就是同一時間對同一片內存空間操做不安全。而atomic
只能保證自身安全不能保證外部訪問安全,解決方法能夠在對可變數組操做的時候加入一個柵欄函數至關於加鎖的功能