一、簡單介紹
上一篇介紹了dispatch_once的實現(xiàn)原理,這一篇將會對dispatch_semaphore進行源碼探究肥荔,關(guān)于信號量的API并不多,主要是三個:create朝群、wait燕耿、signal。
Apple對信號量的描述:
您可以使用調(diào)度信號來調(diào)節(jié)允許同時訪問有限資源的任務(wù)數(shù)量姜胖。
例如誉帅,每個應(yīng)用程序都有一定數(shù)量的文件描述符可供使用。
如果您有一個處理大量文件的任務(wù)右莱,那么您不想同時打開太多的文件而耗盡文件描述符堵第,那么您可以使用信號量來限制文件處理代碼最多使用的文件描述符的數(shù)量。
聽起來比較費勁隧出,舉個 百度百科上停車位的例子就一目了然了:
以一個停車場的運作為例踏志。簡單起見,假設(shè)停車場只有三個車位胀瞪,一開始三個車位都是空的针余。這時如果同時來了五輛車,看門人允許其中三輛直接進入凄诞,然后放下車攔圆雁,剩下的車則必須在入口等待,此后來的車也都不得不在入口處等待帆谍。這時伪朽,有一輛車離開停車場,看門人得知后汛蝙,打開車攔烈涮,放入外面的一輛進去朴肺,如果又離開兩輛,則又可以放入兩輛坚洽,如此往復(fù)戈稿。在這個停車場系統(tǒng)中,車位是公共資源讶舰,每輛車好比一個線程鞍盗,看門人起的就是信號量的作用
二、dispatch semaphore工作原理
dispatch_semaphore與傳統(tǒng)信號量工作原理類似跳昼。但是在資源可用的情況下般甲,使用GCD semaphore將會消耗較少的時間,因為在這種情況下GCD不會調(diào)用內(nèi)核鹅颊,只有在資源不可用的時候才會調(diào)用內(nèi)核敷存,并且系統(tǒng)需要停在你的線程里,直到線程發(fā)出可用信號挪略。
三历帚、源碼
semaphore.h
/*!
* @function dispatch_semaphore_create
*
* @abstract
* 用初始值創(chuàng)建新的計數(shù)信號量。
*
* @discussion
* 當(dāng)兩個線程需要協(xié)調(diào)特定事件的完成時杠娱,將值傳遞給零值非常有用挽牢。
* 傳遞大于零的值對于管理池大小等于該值的有限資源池非常有用
*
* @param value
* 信號量的初始值.傳遞一個小于零的值將導(dǎo)致返回NULL
*
* @result
* 創(chuàng)建成功返回創(chuàng)建的信號量,創(chuàng)建失敗返回NULL.
*/
dispatch_semaphore_t
dispatch_semaphore_create(long value);
/*!
* @function dispatch_semaphore_wait
*
* @abstract
* 等待(遞減)信號量摊求。
*
* @discussion
* 信號量減一禽拔,如果結(jié)果值小于零,則此函數(shù)在返回之前以FIFO順序等待室叉。
*
* @param dsema
* 信號量
*
* @param timeout
* 何時超時(請參閱dispatch_time)睹栖。 為了方便起見,有DISPATCH_TIME_NOW = 0和DISPATCH_TIME_FOREVER = ~0ull兩個常量茧痕。
*
* @result
* 成功時返回零野来,如果發(fā)生超時則返回非零值
*/
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
/*!
* @function dispatch_semaphore_signal
*
* @abstract
* 信號(增加)一個信號量
*
* @discussion
* 信號量加1。如果前一個值小于零踪旷,則此函數(shù)在返回之前喚醒等待的線程
*
* @param dsema
* 在這個參數(shù)中傳遞NULL的結(jié)果是未定義的.
*
* @result
* 如果線程被喚醒曼氛,該函數(shù)返回非零值。否則令野,返回零.
*/
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
以上就是關(guān)于信號量的聲明文件舀患,沒有什么好講的,我也對源碼進行了詳盡的注釋气破,如果還有問題請留言聊浅。
semaphore.c關(guān)于信號量部分
#pragma mark -
#pragma mark dispatch_semaphore_t
struct dispatch_semaphore_vtable_s {
DISPATCH_VTABLE_HEADER(dispatch_semaphore_s);
};
const struct dispatch_semaphore_vtable_s _dispatch_semaphore_vtable = {
.do_type = DISPATCH_SEMAPHORE_TYPE,
.do_kind = "semaphore",
.do_dispose = _dispatch_semaphore_dispose,
.do_debug = _dispatch_semaphore_debug,
};
dispatch_semaphore_t dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
// 如果內(nèi)部值為負(fù)數(shù),則該值的絕對值等于等待線程的數(shù)量。因此低匙,用負(fù)值初始化信號量是虛假的
if (value < 0) {
return NULL;
}
dsema = calloc(1, sizeof(struct dispatch_semaphore_s));
/*
const struct dispatch_semaphore_vtable_s _dispatch_semaphore_vtable = {
.do_type = DISPATCH_SEMAPHORE_TYPE,
.do_kind = "semaphore",
.do_dispose = _dispatch_semaphore_dispose,
.do_debug = _dispatch_semaphore_debug,
};
DISPATCH_QUEUE_PRIORITY_DEFAULT優(yōu)先級的全局隊列:
{
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_vtable = &_dispatch_semaphore_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.dgq_thread_pool_size = MAX_THREAD_COUNT,
.dq_label = "com.apple.root.default-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 7,
}
*/
if (fastpath(dsema)) {
dsema->do_vtable = &_dispatch_semaphore_vtable;
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_ref_cnt = 1;
dsema->do_xref_cnt = 1;
dsema->do_targetq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dsema->dsema_value = value;
dsema->dsema_orig = value;
#if USE_POSIX_SEM
int ret = sem_init(&dsema->dsema_sem, 0, 0);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
#endif
}
return dsema;
}
#if USE_MACH_SEM
static void _dispatch_semaphore_create_port(semaphore_t *s4) {
kern_return_t kr;
semaphore_t tmp;
if (*s4) {
return;
}
// lazily allocate the semaphore port
// Someday:
// 1) Switch to a doubly-linked FIFO in user-space.
// 2) User-space timers for the timeout.
// 3) Use the per-thread semaphore port.
while ((kr = semaphore_create(mach_task_self(), &tmp, SYNC_POLICY_FIFO, 0))) {
DISPATCH_VERIFY_MIG(kr);
sleep(1);
}
if (!dispatch_atomic_cmpxchg(s4, 0, tmp)) {
kr = semaphore_destroy(mach_task_self(), tmp);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
_dispatch_safe_fork = false;
}
#endif
static void _dispatch_semaphore_dispose(dispatch_semaphore_t dsema)
{
// 當(dāng)前可用資源數(shù)目小于原始值旷痕,表示有線程正在執(zhí)行任務(wù),不可被dispose
if (dsema->dsema_value < dsema->dsema_orig) {
printf("BUG IN CLIENT OF LIBDISPATCH: Semaphore/group object deallocated while in use");
}
#if USE_MACH_SEM
kern_return_t kr;
// 銷毀dsema_port
if (dsema->dsema_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
// 有線程正在等待努咐,銷毀dsema_waiter_port
if (dsema->dsema_waiter_port) {
kr = semaphore_destroy(mach_task_self(), dsema->dsema_waiter_port);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
#elif USE_POSIX_SEM
int ret = sem_destroy(&dsema->dsema_sem);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
#endif
_dispatch_dispose(dsema);
}
static size_t _dispatch_semaphore_debug(dispatch_semaphore_t dsema, char *buf, size_t bufsiz)
{
size_t offset = 0;
offset += snprintf(&buf[offset], bufsiz - offset, "%s[%p] = { ",
dx_kind(dsema), dsema);
offset += _dispatch_object_debug_attr(dsema, &buf[offset], bufsiz - offset);
#if USE_MACH_SEM
offset += snprintf(&buf[offset], bufsiz - offset, "port = 0x%u, ",
dsema->dsema_port);
#endif
offset += snprintf(&buf[offset], bufsiz - offset,
"value = %ld, orig = %ld }", dsema->dsema_value, dsema->dsema_orig);
return offset;
}
DISPATCH_NOINLINE
long
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
_dispatch_retain(dsema);
// 僅僅是將dsema->dsema_sent_ksignals值加1
(void)dispatch_atomic_inc2o(dsema, dsema_sent_ksignals);
// 創(chuàng)建semaphore_t
_dispatch_semaphore_create_port(&dsema->dsema_port);
// 核心:利用系統(tǒng)的信號量庫實現(xiàn)發(fā)送信號量的功能苦蒿,表示現(xiàn)在可用的資源數(shù)目+1殴胧,這里是可創(chuàng)建的用于并行線程數(shù)目+1
kern_return_t kr = semaphore_signal(dsema->dsema_port);
// 如果kr返回不為真渗稍,打印錯誤
do {
if (kr) {
printf("BUG IN CLIENT OF LIBDISPATCH: flawed group/semaphore logic");
}
} while (0);
_dispatch_release(dsema);
return 1;
}
long dispatch_semaphore_signal(dispatch_semaphore_t dsema) {
// dispatch_atomic_release_barrier();
// __sync_add_and_fetch((p), (v))
// dispatch_atomic_inc2o(dsema, dsema_value)
long value = dsema->dsema_value + 1;
if (value > 0) {
return 0;
}
if (slowpath(value == LONG_MIN)) {// 輸出錯誤日志
printf("BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
DISPATCH_NOINLINE
static long
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long orig;
again:
// Mach信號似乎有時會虛假地醒來,因此,我們保持一個Mach信號量被發(fā)信號的次數(shù)的平行計數(shù)6880961
// 判斷dsema->dsema_sent_ksignals與orig是否相等团滥,如果相等就返回YES竿屹,并將orig - 1的值賦給dsema->dsema_sent_ksignals
while ((orig = dsema->dsema_sent_ksignals)) {
if ((long)(dsema->dsema_sent_ksignals) == orig) {
dsema->dsema_sent_ksignals = orig - 1;
return 0;
}
}
#if USE_MACH_SEM
mach_timespec_t _timeout;
kern_return_t kr;
_dispatch_semaphore_create_port(&dsema->dsema_port);
// From xnu/osfmk/kern/sync_sema.c:
// wait_semaphore->count = -1; /* we don't keep an actual count */
//
// The code above does not match the documentation, and that fact is
// not surprising. The documented semantics are clumsy to use in any
// practical way. The above hack effectively tricks the rest of the
// Mach semaphore logic to behave like the libdispatch algorithm.
switch (timeout) {
default:
do {
uint64_t nsec = _dispatch_timeout(timeout);
_timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
_timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));
} while (kr == KERN_ABORTED);
if (kr != KERN_OPERATION_TIMED_OUT) {
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
// Fall through and try to undo what the fast path did to
// dsema->dsema_value
case DISPATCH_TIME_NOW:
while ((orig = dsema->dsema_value) < 0) {
if (dispatch_atomic_cmpxchg2o(dsema, dsema_value, orig, orig + 1)) {
return KERN_OPERATION_TIMED_OUT;
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
do {
kr = semaphore_wait(dsema->dsema_port);
} while (kr == KERN_ABORTED);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
#elif USE_POSIX_SEM
struct timespec _timeout;
int ret;
switch (timeout) {
default:
do {
uint64_t nsec = _dispatch_timeout(timeout);
_timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
_timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
ret = slowpath(sem_timedwait(&dsema->dsema_sem, &_timeout));
} while (ret == -1 && errno == EINTR);
if (ret == -1 && errno != ETIMEDOUT) {
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
break;
}
// Fall through and try to undo what the fast path did to
// dsema->dsema_value
case DISPATCH_TIME_NOW:
while ((orig = dsema->dsema_value) < 0) {
if (dispatch_atomic_cmpxchg2o(dsema, dsema_value, orig, orig + 1)) {
errno = ETIMEDOUT;
return -1;
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
do {
ret = sem_wait(&dsema->dsema_sem);
} while (ret != 0);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
break;
}
#endif
goto again;
}
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// 調(diào)用GCC內(nèi)置的函數(shù)__sync_sub_and_fetch,實現(xiàn)減法的原子性操作灸姊。
// __sync_sub_and_fetch(p, v)
// long value = dsema->dsema_value - 1;
long value = dispatch_atomic_dec2o(dsema, dsema_value);
// value大于等于0 就立刻返回
if (fastpath(value >= 0)) {
return 0;
}
// value < 0 進入等待狀態(tài)
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
四拱燃、源碼剖析
先講頭文件聲明的那三個函數(shù)即信號量的create、wait力惯、signal
1.信號量的創(chuàng)建
dispatch_semaphore_t dispatch_semaphore_create(long value)
{
dispatch_semaphore_t dsema;
#1
if (value < 0) {
return NULL;
}
#2
dsema = calloc(1, sizeof(struct dispatch_semaphore_s));
#3
if (dsema) {
dsema->do_vtable = &_dispatch_semaphore_vtable;
dsema->do_next = DISPATCH_OBJECT_LISTLESS;
dsema->do_ref_cnt = 1;
dsema->do_xref_cnt = 1;
dsema->do_targetq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dsema->dsema_value = value;
dsema->dsema_orig = value;
}
return dsema;
}
注:只研究USE_MACH_SEM情況碗誉,其他操作系統(tǒng)請自行查看
這個函數(shù)就一個參數(shù),這個值的含義就是允許最大并行執(zhí)行線程的數(shù)目父晶。
1哮缺、判斷value參數(shù)的合法性
2、分配內(nèi)存
3甲喝、填充信號量結(jié)構(gòu)體尝苇,返回信號量
do_vtable = &_dispatch_queue_root_vtable
dsema->do_targetq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
展開之后:
const struct dispatch_semaphore_vtable_s
_dispatch_semaphore_vtable = {
.do_type = DISPATCH_SEMAPHORE_TYPE,
.do_kind = "semaphore",
.do_dispose = _dispatch_semaphore_dispose,
.do_debug = _dispatch_semaphore_debug,
};
DISPATCH_QUEUE_PRIORITY_DEFAULT優(yōu)先級的全局隊列:
{
.do_vtable = &_dispatch_queue_root_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
.do_vtable = &_dispatch_semaphore_vtable,
.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
.dgq_thread_pool_size = MAX_THREAD_COUNT,
.dq_label = "com.apple.root.default-overcommit-priority",
.dq_running = 2,
.dq_width = UINT32_MAX,
.dq_serialnum = 7,
}
2、信號量的等待
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
// 調(diào)用GCC內(nèi)置的函數(shù)__sync_sub_and_fetch埠胖,實現(xiàn)減法的原子性操作糠溜。
// __sync_sub_and_fetch(p, v)
// long value = dsema->dsema_value - 1;
long value = dispatch_atomic_dec2o(dsema, dsema_value);
// value大于等于0 就立刻返回
if (fastpath(value >= 0)) {
return 0;
}
// value < 0 進入等待狀態(tài)
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
注:真正實現(xiàn)等待邏輯的函數(shù)是最后一行代碼_dispatch_semaphore_wait_slow()函數(shù)
static long
_dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long orig;
again:
// Mach信號似乎有時會虛假地醒來,因此,我們保持一個Mach信號量被發(fā)信號的次數(shù)的平行計數(shù)6880961
// 判斷dsema->dsema_sent_ksignals與orig是否相等直撤,如果相等就返回YES非竿,并將orig - 1的值賦給dsema->dsema_sent_ksignals
while ((orig = dsema->dsema_sent_ksignals)) {
if ((long)(dsema->dsema_sent_ksignals) == orig) {
dsema->dsema_sent_ksignals = orig - 1;
return 0;
}
}
mach_timespec_t _timeout;
kern_return_t kr;
_dispatch_semaphore_create_port(&dsema->dsema_port);
// From xnu/osfmk/kern/sync_sema.c:
// wait_semaphore->count = -1; /* we don't keep an actual count */
//
// The code above does not match the documentation, and that fact is
// not surprising. The documented semantics are clumsy to use in any
// practical way. The above hack effectively tricks the rest of the
// Mach semaphore logic to behave like the libdispatch algorithm.
switch (timeout) {
default:
do {
uint64_t nsec = _dispatch_timeout(timeout);
_timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
_timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));
} while (kr == KERN_ABORTED);
if (kr != KERN_OPERATION_TIMED_OUT) {
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
// Fall through and try to undo what the fast path did to
// dsema->dsema_value
case DISPATCH_TIME_NOW:
while ((orig = dsema->dsema_value) < 0) {
if (dispatch_atomic_cmpxchg2o(dsema, dsema_value, orig, orig + 1)) {
return KERN_OPERATION_TIMED_OUT;
}
}
// Another thread called semaphore_signal().
// Fall through and drain the wakeup.
case DISPATCH_TIME_FOREVER:
do {
kr = semaphore_wait(dsema->dsema_port);
} while (kr == KERN_ABORTED);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
break;
}
goto again;
}
刪掉多余操作系統(tǒng)的分支,剩下的代碼很簡練谋竖,這個函數(shù)針對不同的 timeout 參數(shù)红柱,分了三種情況考慮:
①DISPATCH_TIME_NOW它的值為0,也就是說超時時間為0圈盔,如果是這種情況的話豹芯,相當(dāng)于
while ((orig = dsema->dsema_value) < 0) {
if (dsema->dsema_value == orig) {
dsema->dsema_value = orig + 1;
return KERN_OPERATION_TIMED_OUT;
}
}
函數(shù)立即返回值KERN_OPERATION_TIMED_OUT即49
②DISPATCH_TIME_FOREVER它的值為0按位取反,是一個很小的負(fù)數(shù)
這時執(zhí)行系統(tǒng)庫的等待函數(shù)semaphore_wait驱敲,直到semaphore_wait返回KERN_ABORTED即為14為止铁蹈。
③default分支,我們指定一個超時時間,這和 DISPATCH_TIME_FOREVER 的處理比較類似
不同的是我們調(diào)用了內(nèi)核提供的semaphore_timedwait方法可以指定超時時間
可見信號量被喚醒后握牧,會回到最開始的地方容诬,進入while循環(huán)。
這個判斷條件一般都會成立
進入while循環(huán)后沿腰,if 判斷一定成立览徒,因此返回0
正如文檔所說,返回 0 表示成功颂龙,否則表示超時
3习蓬、信號量的signal(實在不知道怎么翻譯啊~~)
long dispatch_semaphore_signal(dispatch_semaphore_t dsema) {
// dispatch_atomic_release_barrier();
// __sync_add_and_fetch((p), (v))
// dispatch_atomic_inc2o(dsema, dsema_value)
long value = dsema->dsema_value + 1;
if (value > 0) {
return 0;
}
if (slowpath(value == LONG_MIN)) {// 輸出錯誤日志
printf("BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
這個函數(shù)真正的實現(xiàn)是在_dispatch_semaphore_signal_slow()函數(shù)里面。
long
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
_dispatch_retain(dsema);
// 僅僅是將dsema->dsema_sent_ksignals值加1
(void)dispatch_atomic_inc2o(dsema, dsema_sent_ksignals);
// 創(chuàng)建semaphore_t
_dispatch_semaphore_create_port(&dsema->dsema_port);
// 核心:利用系統(tǒng)的信號量庫實現(xiàn)發(fā)送信號量的功能措嵌,表示現(xiàn)在可用的資源數(shù)目+1躲叼,這里是可創(chuàng)建的用于并行線程數(shù)目+1
kern_return_t kr = semaphore_signal(dsema->dsema_port);
// 如果kr返回不為真,打印錯誤
do {
if (kr) {
printf("BUG IN CLIENT OF LIBDISPATCH: flawed group/semaphore logic");
}
} while (0);
_dispatch_release(dsema);
return 1;
}
注:核心代碼就是semaphore_signal(),利用系統(tǒng)的信號量庫實現(xiàn)發(fā)送信號量的功能企巢,表示現(xiàn)在可用的資源數(shù)目+1枫慷,這里是可創(chuàng)建的用于并行執(zhí)行的線程數(shù)目+1,如果發(fā)現(xiàn)這與源碼有出入的話浪规,那就是我對一些宏進行了展開或听,還有一些無用的代碼進行了刪減,并不影響我們理解原理笋婿。
至此誉裆,我們把信號量的源碼剖析完了,下一篇繼續(xù)分析semaphore.c實現(xiàn)文件里面其余實現(xiàn)萌抵,主要分析的是dispatch_group相關(guān)的實現(xiàn)