GCD
全稱 Grand Central Dispatch
,是Apple開發的一個多核編程解決辦法。該方法在Mac OSX 10.6 雪豹 中首次推出,隨後引入到IOS4.0中。GCD是一個替代NSThread
,NSOperationQueue
等技術的方案。php
// 1.隊列
dispatch_queue_t queue = dispatch_queue_create("Typeco", DISPATCH_QUEUE_CONCURRENT);
// 2.任務:無參數,無返回值
void(^block)(void) = ^{
NSLog(@"這是要執行的任務");
};
// 3.函數
dispatch_async(queue, block);
複製代碼
這裏爲了區分開任務和函數,我把block單獨拆出來,這樣對GCD的認識會更簡單直觀,總結一下就是:git
GCD 就是將指定的任務(block)添加到指定的隊列(queue),而後指定 dispatch 函數執行。任務的執行遵循隊列的FIFO原則:先進先出。github
串行隊列(Serial)web
隊列裏的任務一個一個執行,一個任務執行完畢,才執行下一個任務。自定義串行隊列以下:編程
dispatch_queue_t queue = dispatch_queue_create("Typeco", DISPATCH_QUEUE_SERIAL);
複製代碼
其中第一個參數label是用來標識queue的字符串,第二個參數標識隊列的類型,這裏咱們要建立串行隊列:DISPATCH_QUEUE_SERIAL
。api
#define DISPATCH_QUEUE_SERIAL NULL安全
PS : 串行隊列的第二個參數咱們傳NULL的效果是同樣的。多線程
併發隊列(Concurrent)併發
容許多個任務並行(同時)執行,可是執行任務的順序是隨機,具體要看CPU的調度狀況,這塊後續會說到。app
dispatch_queue_t queue = dispatch_queue_create("Typeco", DISPATCH_QUEUE_CONCURRENT);
複製代碼
這裏同上,不過第二個參數咱們選擇 DISPATCH_QUEUE_CONCURRENT
系統隊列
dispatch_get_main_queue()
主隊列是應用 程序啓動時(main函數以前),系統自動建立的惟一一個串行隊列,而且該隊列與主線程綁定。dispatch_get_global_queue(0,0)
全局併發隊列,這裏一般咱們沒有特別需求的狀況下,默認都傳0便可。全局併發隊列只可獲取而不能建立。知道了隊列的含義,咱們就瞭解了任務加到隊列中是如何執行的,接下來咱們就要看函數是如何影響隊列任務執行的。
同步函數 dispatch_sync()
異步函數 dispatch_async()
線程和隊列的關係
具體測試可前往 Demo 進行下載!
區別 | 串行 | 併發 | 主隊列 |
---|---|---|---|
同步 | 1.不會開啓線程,在當前線程執行任務 2.任務一個接一個執行 3.會產生堵塞 |
1.不會開啓線程,在當前線程執行任務 2.任務一個接一個執行 |
1.死鎖卡住不執行 |
異步 | 1.開啓一條新線程 2.任務一個接一個執行 |
1.開啓線程,在當前線程執行任務 2.任務異步執行,沒有順序,cpu調度有關 |
1.不會開啓新線程,依舊在主線程串行執行任務 |
因而可知,線程和隊列並無直接聯繫。
首先能夠去 GCD源碼 進行下載查看。
咱們大概分析一下隊列是如何建立的,首先dispatch_queue_create的第一個參數是一個字符串爲了標識queue,第二個參數表明的是串行仍是併發,那麼咱們研究的重點就放在第二個參數上:
/*
初始方法,依次會調用下方核心代碼
*/
dispatch_queue_create("sync_serial", DISPATCH_QUEUE_SERIAL);
/*
主要看下面的方法實現
此處dqa便是上面的 DISPATCH_QUEUE_SERIAL
*/
_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,
dispatch_queue_t tq, bool legacy)
// 此處省略大量代碼...
// 開闢內存 - 生成響應的對象 queue
dispatch_lane_t dq = _dispatch_object_alloc(vtable,
sizeof(struct dispatch_lane_s));
/*
構造方法,此處有關鍵點,若是是dqai_concurrent 那麼隊列寬度 DISPATCH_QUEUE_WIDTH_MAX 不然爲 1
也就是說串行隊列寬度爲1,併發沒有限制
*/
_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
// 標籤
dq->dq_label = label;
// 優先級
dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
dqai.dqai_relpri);
/*
此處對queue進行保留,api裏有描述若是非ARC 咱們建立完以後要調用 dispatch_release
*/
_dispatch_retain(tq);
return dq;
複製代碼
以上只是大概分析一下 dispatch_queue_create 是如何建立隊列以及隊列是如何區分串行和併發的,相關細節還請下載源碼進行閱讀。
研究信號量無非就是關注其三個方法:
dispatch_semaphore_create
dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
// If the internal value is negative, then the absolute of the value is
// equal to the number of waiting threads. Therefore it is bogus to
// initialize the semaphore with a negative value.
if (value < 0) {
return DISPATCH_BAD_INPUT;
}
dsema = _dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
sizeof(struct dispatch_semaphore_s));
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_targetq = _dispatch_get_default_queue(false);
dsema->dsema_value = value;
_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
dsema->dsema_orig = value;
return dsema;
}
複製代碼
其實就是一個初始化dispatch_semaphore_t
並賦值的過程,其賦值主要在**dema_value
**上,記住這個字段,可能咱們在後面的分析中要用到.
dispatch_semaphore_signal
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
複製代碼
os_atomic_inc2o
---->os_atomic_add2o(p, f, 1, m)
---->os_atomic_add(&(p)->f, (v), m)
---->_os_atomic_c11_op((p), (v), m, add, +)
----> 得出結果deem_value
+1,也就是說上述os_atomic_inc2o
返回的結果是在以前value的基礎上進行+1,若是>0當即返回0
dispatch_semaphore_wait
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// value++
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
複製代碼
os_atomic_dec2o的原理跟上面那個os_atomic_inc2o差很少,只不過此次運算符是 - 號,因此總結一下就是,對信號量進行減1操做,若是大於等於0返回0
那麼縱觀這兩個方法給咱們看到的僅僅是對一個數字(信號量)進行加減的操做,那麼它在底層是如何影響咱們的線程的呢?
do {
_dispatch_trace_runtime_event(worker_unpark, dq, 0);
_dispatch_root_queue_drain(dq, pri, DISPATCH_INVOKE_REDIRECTING_DRAIN);
_dispatch_reset_priority_and_voucher(pp, NULL);
_dispatch_trace_runtime_event(worker_park, NULL, 0);
} while (dispatch_semaphore_wait(&pqc->dpq_thread_mediator,
dispatch_time(0, timeout)) == 0);
複製代碼
在queue.c文件中,找到了上述代碼,其實就是一個do..while 循環,只要wait返回0就一直讓queue處於一個推遲執行(park)的狀態,結合咱們上述wait方法的理解能夠得出,只要信號量爲>=0 當前隊列繼續FIFO,不然一直等待直到wait返回0,當執行signal方法以後信號量會執行+1操做,這個時候就會打破上述循環。
綜上咱們一般這麼使用信號量:
- (void)demo {
dispatch_semaphore_t sema = dispatch_semaphore_create(1);
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 耗時操做,異步操做
sleep(5);
dispatch_semaphore_signal(sema);
});
}
複製代碼
初始化1的信號量,表明了單線程一次只能有一個線程訪問,好比當前咱們線程1已經執行了wait方法,這個時候信號量爲0,當另一個線程2來訪問這個異步耗時操做時候就會處於上述的do...while 循環等待中,直到線程1執行signal 對信號量執行+1操做,這個時候纔會打破這個循環讓線程2能訪問該操做。
這個是爲了解決防止多線程同時訪問同一個資源形成的安全問題,咱們還可使用信號量達到barrier (柵欄)的做用:
- (void)demo {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 執行方法1
NSLog(@"執行方法1");
sleep(5);
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//執行方法2
NSLog(@"執行方法2");
});
}
複製代碼
此處惟一的區別的就是初始化的時候賦值爲0,默認wait等待,直到異步線程signal,才能越過wait執行後續的任務,達到一個柵欄的效果。
如下都是基於libdispatch源碼分析的結果: 源碼參考,源碼部份內容比較多,詳細代碼請下載查看,這裏儘可能少粘貼代碼,力求用流程圖來展現大概過程:
dispatch_group 相關代碼都在dispatch_semaphore.c模塊中,可見dispatch_group是一個基於信號量的同步機制,核心功能主要是下面幾個函數:
圖中共有4組控制流:
最好就是誰來喚醒notify,全部的async和普通的方式都會走enter和leave,因此信號的判斷就是enter和leave的配對,正如api所述,enter和leave要成對出現,若是使用async則不用擔憂,由於其內部幫你成對的實現了:
void
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_retain(group);
dispatch_group_enter(group);
dispatch_async(queue, ^{
block();
dispatch_group_leave(group);
dispatch_release(group);
});
}
複製代碼
因此每條控制流執行leave的時候都要檢查信號量是否知足,若是知足則執行notify,不然等待,既然關鍵是leave方法的執行觸發notify,因此就能夠重點看看leave的實現:
void
dispatch_group_leave(dispatch_group_t dg)
{
// The value is incremented on a 64bits wide atomic so that the carry for
// the -1 -> 0 transition increments the generation atomically.
uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,
DISPATCH_GROUP_VALUE_INTERVAL, release);
uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);
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 {
// If the group was entered again since the atomic_add above,
// we can't clear the waiters bit anymore as we don't know for
// which generation the waiters are for
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()");
}
}
複製代碼
其中有一個do...while循環,每次對狀態進行比對,直到狀態符合調用wake方法喚醒group。
(異步請求1 + 異步請求2) ==> 異步請求3,請求3依賴請求1和請求2的返回
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"異步請求1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
sleep(3);
NSLog(@"異步請求2");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"1和2執行完了異步請求3");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"異步請求4");
});
複製代碼
GCD_Demo[19428:6087378] 異步請求1
GCD_Demo[19428:6087375] 異步請求4
GCD_Demo[19428:6087377] 異步請求2
GCD_Demo[19428:6087377] 1和2執行完了異步請求3
這裏我故意在notify後面多加了個4,發現notify這個是不分先後順序的。
enter + leave
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"異步請求1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"異步請求2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"1和2執行完了異步請求3");
});
複製代碼
達到的效果是同樣的,只不過這裏沒有用到group_async 是同樣能達到相應目的。
至此GCD分析告一段落,想dispatch_source 以及柵欄函數相關後續還會陸續分享,歡迎留言交流。
我的小站 www.typeco.cn 歡迎光臨。