本文首发于 我的博客git
GCD
全称 Grand Central Dispatch
,是Apple开发的一个多核编程解决办法。该方法在Mac OSX 10.6 雪豹 中首次推出,随后引入到IOS4.0中。GCD是一个替代NSThread
,NSOperationQueue
等技术的方案。github
// 1.队列
dispatch_queue_t queue = dispatch_queue_create("Typeco", DISPATCH_QUEUE_CONCURRENT);
// 2.任务:无参数,无返回值
void(^block)(void) = ^{
NSLog(@"这是要执行的任务");
};
// 3.函数
dispatch_async(queue, block);
复制代码
这里为了区分开任务和函数,我把block单独拆出来,这样对GCD的认识会更简单直观,总结一下就是:编程
GCD 就是将指定的任务(block)添加到指定的队列(queue),而后指定 dispatch 函数执行。任务的执行遵循队列的FIFO原则:先进先出。api
串行队列(Serial)安全
队列里的任务一个一个执行,一个任务执行完毕,才执行下一个任务。自定义串行队列以下:markdown
dispatch_queue_t queue = dispatch_queue_create("Typeco", DISPATCH_QUEUE_SERIAL);
复制代码
其中第一个参数label是用来标识queue的字符串,第二个参数标识队列的类型,这里咱们要建立串行队列:DISPATCH_QUEUE_SERIAL
。多线程
#define DISPATCH_QUEUE_SERIAL NULL并发
PS : 串行队列的第二个参数咱们传NULL的效果是同样的。异步
并发队列(Concurrent)async
容许多个任务并行(同时)执行,可是执行任务的顺序是随机,具体要看CPU的调度状况,这块后续会说到。
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分析告一段落,欢迎点评交流。