GCD是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时GCD抽象层次最高,当然是用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。
GCD是一种轻量的基于block的线程模型,底层实现主要有Dispatch Queue和Dispatch Source Dispatch Queue :管理block(操作) Dispatch Source :处理事件 Dispatch Queue&Dispatch Source更多相关知识
GCD推出来以后,开发者可以不直接操纵线程,而是将所要执行的任务封装成一个unit丢给线程池去处理,线程池会有效管理线程的并发,控制线程的生死。因此,现在如果考虑到并发场景,基本上是围绕着GCD和NSOperationQueue来展开讨论。
这里就不得不提到两个概念:任务和队列
同步派发(sync) 和 异步派发(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!
【异步并不一定会开启多线程,当在主线程中派发任务到主队列后,会等待主线程空闲时才会调度该任务并没有开启新的线程;添加到其他线程时,会开启新的线程调度任务。】 如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。 如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。
放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
GCD中不同队列中不同任务的执行情况如下表:
同步执行的任务 | 异步执行的任务 | |
---|---|---|
串行队列中 | 当前线程,一个一个执行 | 其他线程,一个一个执行 |
并行队列中 | 当前线程,一个一个执行 | 同时开很多线程,一起执行 |
队列的创建和执行 #开辟队列的方法: dispatch_queue_t myQueue = dispatch_queue_create("MyQueue", NULL);
参数1:标签,用于区分队列
参数2:队列的类型,表示这个队列是串行队列还是并发队列NUll表示串行队列,
DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。
DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。
#执行队列的方法
异步执行
dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
同步执行
dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
几个特别的队列
这里需要特别说一下:主队列和主线程的关系。 (1)主队列是专门负责调度主线程的任务的。iOS编程中,需要在主线程(主线程,这个线程是其他线程的父线程)中进行操作时,我们经常会用到以下代码: dispatch_async(dispatch_get_main_queue(), ^{ // dispatch_get_main_queue() 实际获取的是 主队列 }); 而且在主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行。比如block内的任务是异步执行,主线程在将当前方法执行完毕之后,才会去继续执行主队列里的任务。 (2)主队列的任务一定在主线程中执行,主线程是可以执行主队列之外(dispatch_get_global_queue)其他队列的任务的。 另外关于主线程中更新UI操作也不是绝对安全的,详细请看这篇文章:主线程中也不绝对安全的 UI 操作
队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。这是一个很实用的功能。
# 创建队列组
dispatch_group_t group = dispatch_group_create();
# 获取到全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
# 多次使用队列组的方法执行任务, 目前API中只有异步方法
//1.执行3次循环
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.执行5次循环
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
#.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
队列组其他实用方法 这也是使用GCD信号量实现多线程同步加锁的实现方式。
dispatch_group_enter
用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞和dispatch_group_notify的block执行
void dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave
用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter和dispatch_group_leave要匹配,不然系统会认为group任务没有执行完毕
void dispatch_group_leave(dispatch_group_t group);
dispatch_group_wait
等待组任务完成,会阻塞当前线程,当任务组执行完毕时,才会解除阻塞当前线程
long dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout);
***********************************
group ——需要等待的任务组
timeout ——等待的超时时间(即等多久),单位为dispatch_time_t。
如果设置为DISPATCH_TIME_FOREVER,则会一直等待(阻塞当前线程),直到任务组执行完毕。
当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是 dispatch_semaphore.
信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore dispatch_semaphore_signal 发送一个信号 dispatch_semaphore_wait 等待信号 第一个函数有一个整形的参数,我们可以理解为信号的总量; dispatch_semaphore_signal是发送一个信号,自然会让信号总量+1, dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1, 特别说明下:信号总量为0时dispatch_semaphore_wait会阻塞当前的线程(主线程、其他线程),被阻塞的线程中无法执行任何代码。 根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。
#重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
dispatch_apply():
#单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
dispatch_once():
#延迟一定的时间后执行。
dispatch_time():
#使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
dispatch_barrier_async():
#实现对任务分组管理,如果一组任务全部完成可以通过
dispatch_group_async():
#方法获得完成通知(需要定义dispatch_group_t作为分组标识)。
dispatch_group_notify()
GCD的知识很多,本文就不一一罗列了,后续如有新的收获,会持续更新的。