GCD終章

這是我參與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(@"**********起來幹!!");
}
複製代碼

image.png 這裏使用了異步併發隊列,在異步併發的時候使用柵欄函數,前面的任務執行完畢纔會來到這裏,可是不會阻塞後面任務的執行,因此步驟3柵欄函數必然在步驟一、2以後執行。步驟一、2無序,步驟四、5無序。 注意:若是咱們把這個併發隊列換成全局併發隊列呢?markdown

dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
複製代碼

image.png 咱們發現此時柵欄函數並無效果了,也就是說在併發隊列中的柵欄函數在全局併發隊列中失效了,那麼爲何呢?咱們此時照例須要上一份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_wakeupless

#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_completeasync

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);
    });
 }
複製代碼

image.png

任務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(), ^{

    });
    
}
複製代碼

方案二: enterleave搭配

- (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

GCDrunLoop實際上是同等級,沒有所謂的歸屬關係,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只能保證自身安全不能保證外部訪問安全,解決方法能夠在對可變數組操做的時候加入一個柵欄函數至關於加鎖的功能

相關文章
相關標籤/搜索