iOS多线程之GCD浅析
本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法,分析GCD的原理,总结GCD的用法和需要注意的地方。
首先来说明一下线程和进程之间的区别:
线程:线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
比方:进程是个小地主,而线程就是小地主下的农户,小地主要活下去,农户就要干活,农户与农户之间的工具资源都是可以共享。
进程:简单说就是程序在计算机上的一次执行活动。进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,
状态:运行、阻塞、挂起阻塞、就绪、挂起就绪
状态之间的转换:准备就绪的进程,被CPU调度执行,变成运行态;
运行中的进程,进行I/O请求或者不能得到所请求的资源,变成阻塞态;
运行中的进程,进程执行完毕(或时间片已到),变成就绪态;
将阻塞态的进程挂起,变成挂起阻塞态,当导致进程阻塞的I/O操作在用户重启进程前完成(称之为唤醒),挂起阻塞态变成挂起就绪态,当用户I/O操作结束之前重启进程,挂起阻塞态变成阻塞态;
将就绪(或运行)中的进程挂起,变成挂起就绪态,当该进程恢复之后,挂起就绪态变成就绪态;
1.GCD的简介:
GCD为Grand
Central Dispatch的缩写。[1]
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6中首次推出,也可在IOS 4及以上版本使用。是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。仍然在一个很低的水平使用线程,但是它不需要程序员关注太多的细节。GCD创建的队列是轻量级的,苹果声明一个GCD的工作单元需要由15个指令组成。也就是说创造一个传统的线程很容易的就会需要几百条指令。中的一个任务可被用于创造一个被放置于队列的工作项目或者事件源。如果一个任务被分配到一个事件源,那么一个由功能或者程序块组成的工作单元会被放置于一个适当的队列中。苹果公司认为GCD相比于普通的一个接一个的执行任务的方式更为有效率。
2.为什么要用 GCD 呢?
因为 GCD 有很多好处啊,具体如下:
快,更快的内存效率,因为线程栈不暂存于应用内存。
稳,提供了自动的和全面的线程池管理机制,稳定而便捷。
准,提供了直接并且简单的调用接口,使用方便,准确。
可用于多核的并行运算
会自动利用更多的 CPU 内核(比如双核、四核)
会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
3.GCD的核心概念:
1.任务:执行什么操作
2.队列:用来存放任务
将任务添加到队列中
1. GCD会自动将队列中的任务取出,放到对应的线程中执行
2. 任务的取出遵循队列的FIFO原则:先进先出,后进后出
队列的类型:
1.同步:只能在当前线程中执行任务,不具备开启新线程的能力
2.异步:可以在新的线程中执行任务,具备开启新线程的能力
GCD中执行两个队列的函数:
• 用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
• 用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
*注意:
主队列中永远只有一条线程-主线程,主线程在等待着主队列调度同步任务,而主队列发现主线程上还有任务未执行完,就不会让同步任务添加到主线程上,由此就造成了互相等待(主队列在等待主线程执行完已有的任务,而主线程又在等待主队列调度同步任务!),此时也就是所谓的死锁了!
GCD默认已经提供了全局的并发队列供整个应用使用,所以可以不用手动创建。在主队列执行同步任务时,我们可以用一个异步任务包裹一个同步任务添加到主队列中!
3. GCD的应用
//创建队列的的集中方式
// 1.生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。
dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL);
// 2.生成一个并发执行队列,block被分发到多个线程去执行
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT);
// 3.并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 4.获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。
dispatch_queue_t queue3 = dispatch_get_main_queue();
// 同步执行:不具备开启新线程的能力 dispatch_sync....
// 异步执行:具备开启新线程的能力 dispatch_async....
//异步执行block,函数立即返回
dispatch_async(queue, ^{
//block具体代码
});
//同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。
//尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。
dispatch_sync(queue, ^{
//block具体代码
dispatch_sync(queue, ^{
//block具体代码
});
});
// 为了避免程序死锁阻塞,用async嵌套mian_queue使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//子线程中开始网络请求数据
//更新数据模型
dispatch_sync(dispatch_get_main_queue(), ^{
//在主线程中更新UI代码
});
});
// 要获取的Dispatch Queue无非就是两种类型:
// Main Dispatch Queue
// Global Dispatch Queue / Concurrent
Dispatch Queue
id dispatchMain = dispatch_get_main_queue();
id dispatchGlobal = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
说明:全局并发队列的优先级
#defineDISPATCH_QUEUE_PRIORITY_HIGH 2 //高
#defineDISPATCH_QUEUE_PRIORITY_DEFAULT 0 //默认(中)
#defineDISPATCH_QUEUE_PRIORITY_LOW (-2) //低
#defineDISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN //后台
*/
// 一般只在需要更新UI时我们才获取Main Dispatch Queue,其他情况下用Global Dispatch Queue就满足需求了;
2. dispatch_after
一般用法
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue里面延迟执行的一段代码
...
});
dispatch_time_t dispatch_time ( dispatch_time_t when, int64_tdelta );
//第一个参数一般是DISPATCH_TIME_NOW,表示从现在开始。第二个参数就是真正的延时的具体时间。
/*
NSEC:纳秒。
USEC:微妙。
SEC:秒
PER:每
*/
应用场景这为我们提供了一个简单的延迟执行的方式,比如在view加载结束延迟执行一个动画等等。
3. dispatch_once
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
double delay = 5;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay *NSEC_PER_SEC)), queue, ^{
NSLog(@"delay
5s");
});
/*
*在此线程中,代码永远只执行一次
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"此代码只执行一次");
});
4.dispatch_apply
//dispatch_apply会将一个指定的block执行指定的次数。如果要对某个数组中的所有元素执行同样的block的时候,这个函数就显得很有用了,用法很简单,指定执行的次数以及Dispatch
Queue,在block回调中会带一个索引,然后就可以根据这个索引来判断当前是对哪个元素进行操作,注意的是这个是同步返回的
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {
/*
*dispatch_apply表现的就像一个for循环一样,但他并不能并发的执行迭代,这个函数是同步的,跟普通的for循环一样直到所有的操作完成之后才会返回
*/
NSLog(@"%zu",i);
});
5.dispatch_barrier_async (栅栏函数)
/*
这个队列是一个Concurrent Dispatch Queue,能同时并发多少线程是由系统决定的,如果添
加dispatch_barrier_async的时候,其他的block(包括上面4个block)还没有开始执行,
那么会先执行dispatch_barrier_async里的任务,其他block全部处于等待状态。如果添加
dispatch_barrier_async的时候,已经有block在执行了,那么
dispatch_barrier_async会等这些block执行完后再执行。
*/
dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
});
dispatch_async(concurrentQueue, ^{
});
dispatch_async(concurrentQueue, ^{
});
dispatch_barrier_sync(concurrentQueue, ^{
});
dispatch_async(concurrentQueue, ^{
});
dispatch_async(concurrentQueue, ^{
});
6. dispatch_group(调度组)
/*
dispatch_group的作用就是将提交到 dispatch_queue 上的 block 进行分组,以 dispatch_group_wait 函数为分界线,然后以组为单位进行组内的任务调度工作。一切都是以 block 为最基本的任务单元来操作
//1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
用dispatch_async.... 添加group同时开启N个子线程
用dispatch_sync.... 添加group同时添加到主线程
//2.创建串行队列(用异步函数往串行队列中添加任务)
dispatch_queue_t queue=dispatch_queue_create("wendingding", NULL);
dispatch_async....
会开启线程,但是只开启一个线程(主线程1,创建的线程为2)
//3.创建串行队列
dispatch_queue_t queue=dispatch_queue_create("wendingding", NULL);
dispatch_sync....
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t groupQueue = dispatch_group_create();
// 开启多条线程,并发执行任务
dispatch_queue_t zsQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t zsGroup = dispatch_group_create();
dispatch_group_async(zsGroup, zsQueue, ^{
NSLog(@"task1");
});
dispatch_group_async(zsGroup, zsQueue, ^{
NSLog(@"task2");
});
dispatch_group_wait(zsGroup, DISPATCH_TIME_FOREVER);
NSLog(@"task1
task2 end waiting");
// 执行完task task2之后才会继续执行task5 task6
zsGroup =dispatch_group_create();
dispatch_group_async(zsGroup, zsQueue, ^{
NSLog(@"task5");
});
dispatch_group_async(zsGroup, zsQueue, ^{
NSLog(@"task6");
});
dispatch_group_wait(zsGroup, DISPATCH_TIME_FOREVER);
NSLog(@"task5
task6 end waiting");
// dispatch_group_wait实际上会使当前的线程处于等待的状态,也就是说如果是在主线程执行dispatch_group_wait,在上面的Block执行完之前,主线程会处于卡死的状态。
dispatch_group_notify(groupQueue, dispatch_get_main_queue(), ^{
NSLog(@"
end waiting");
/*
*dispatch_group_notify以异步的方式工作,当dispatch_group中没有任何任务时,他就会执行其代码,在这个操作中他不会阻塞主线程,所以不需要把方法包裹在async调用中,dispatch_group_notify函数可以得到最后执行完的通知
但是因为他们都是分配给同一个队列dispatch_get_main_queue()中的!在一个队列中是串行的啊。所以,还是按照顺序来
*/
});
7. dispatch_semaphore(信号量)
// 信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数
dispatch_semaphore_t dispatch_semaphore_create(long value);
/*
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create创建一个semaphore
dispatch_semaphore_signal发送一个信号
dispatch_semaphore_wait等待信号
*/
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i
< 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
/*
简单的介绍一下这一段代码,创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。