GCD
队列
- 串行队列
- 并行队列
- 全局队列
- 主队列
几个容易混淆的概念
dispatch_barrier_async栅栏函数
dispatch_group_t队列组
dispatch_semaphore_t信号量
dispatch_apply 快速迭代
dispatch_suspend和dispatch_resume
例:
GCD:
GCD全称 Grand Central Dispatch,我们通俗的翻译叫牛逼的中心调度。
队列
先说下GCD中的队列 dispatch_queue_t
串行队列: 同步执行,在当前线程执行
并行队列: 可由多个线程异步执行,但任务的取出还是FIFO的
//创建队列
//参数1: 队列名
//参数2: 队列类型 DISPATCH_QUEUE_SERIAL | NULL 串行队列,
DISPATCH_QUEUE_CONCURRENT 并发队列
dispatch_queue_t sky_queue = dispatch_queue_create("队列名字", NULL);
全局队列: 顾名思义就是全局队列,由系统创建,属于并行队列
//全局队列
/*
参数1 : iOS8以前是优先级, iOS8以后是服务质量
iOS8以前
* - DISPATCH_QUEUE_PRIORITY_HIGH 高优先级 2
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: 默认的优先级 0
* - DISPATCH_QUEUE_PRIORITY_LOW: 低优先级 -2
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND:
iOS8以后
* - QOS_CLASS_USER_INTERACTIVE 0x21 用户交互(用户迫切想执行任务)
* - QOS_CLASS_USER_INITIATED 0x19 用户需要
* - QOS_CLASS_DEFAULT 0x15 默认
* - QOS_CLASS_UTILITY 0x11 工具(低优先级, 苹果推荐将耗时操作放到这种类型的队列中)
* - QOS_CLASS_BACKGROUND 0x09 后台
* - QOS_CLASS_UNSPECIFIED 0x00 没有设置
参数2: 没用过一般传 0
*/
//例子
dispatch_queue_t sky_global_queue = dispatch_get_global_queue(0, 0);
主队列: 主队列属于串行队列. 运行在主线程(UI线程)
//主队列
dispatch_queue_t sky_main_queue = dispatch_get_main_queue();
几个容易混淆的概念
- **串行队列,同步执行: ** 在当前线程上顺序执行任务,并不会开启新线程.
- 串行队列,异步执行: 在不是主线程的另一个线程上顺序执行任务.(因为是异步执行,如果多线程的话不能保证顺序执行,所以是一个线程。)
- 并发队列,同步执行 : 在当前线程上顺序执行任务,并不会开启新线程. (同串行队列同步执行)
- 并发队列,异步执行: 给每一个任务开启一个线程去执行(因为队列中的所有任务都是异步的)分别执行每个任务.每个任务都是从头开始的.哪个任务先结束由任务本身决定,但是系统都会给每一个任务一个单独的线程去执行。
看看代码:
//串行 同步
- (void)sky_syncSerial
{
//同步+串行 不会开始新线程. 等到同步函数调用完成之后才会执行后面的代码
dispatch_queue_t sky_sync_serial_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_SERIAL);// DISPATCH_QUEUE_SERIAL == NULL
dispatch_sync(sky_sync_serial_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_serial_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_serial_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
NSLog(@"4 ");
/*
运行结果:
2017-01-26 16:16:40.424 GCDNotes[93061:9398100] 1 = <NSThread: 0x608000070100>{number = 1, name = main}
2017-01-26 16:16:40.424 GCDNotes[93061:9398100] 2 = <NSThread: 0x608000070100>{number = 1, name = main}
2017-01-26 16:16:40.424 GCDNotes[93061:9398100] 3 = <NSThread: 0x608000070100>{number = 1, name = main}
2017-01-26 16:16:40.425 GCDNotes[93061:9398100] 4
*/
}
//串行 异步
- (void)sky_asyncSerial
{
//在不是主线程的另一个线程顺序执行任务. 因为是异步所以开启新线程. 因为是串行所以是一个线程.
dispatch_queue_t sky_global_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(sky_global_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(sky_global_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_async(sky_global_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
NSLog(@"4 ");
/*
运行结果:
2017-01-26 18:56:19.132 GCDNotes[93256:9704465] 4
2017-01-26 18:56:19.132 GCDNotes[93256:9704622] 1 = <NSThread: 0x60800026a500>{number = 5, name = (null)}
2017-01-26 18:56:19.133 GCDNotes[93256:9704622] 2 = <NSThread: 0x60800026a500>{number = 5, name = (null)}
2017-01-26 18:56:19.133 GCDNotes[93256:9704622] 3 = <NSThread: 0x60800026a500>{number = 5, name = (null)}
*/
}
//并发 同步
- (void)sky_syncConCurrent
{
//同步 + 并发 : 在同一个线程里面.但不是主线程,如果同步在主线程里面会造成死锁.
//原因: 主队列,如果主线程正在执行代码,就不调度任务;同步执行:一直执行第一个任务直到结束。两者互相等待造成死锁
//错误代码示例:
/*
dispatch_queue_t sky_main_queue = dispatch_get_main_queue();
dispatch_sync(sky_main_queue, ^{
//发送错误,不会走下面的代码 ~
NSLog(@"erroe = %@",sky_main_queue);
});
*/
dispatch_queue_t sky_sync_Concurrent_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(sky_sync_Concurrent_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_Concurrent_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_sync(sky_sync_Concurrent_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
NSLog(@"4 ");
/*
运行结果:
2017-01-26 19:01:34.868 GCDNotes[93272:9727007] 1 = <NSThread: 0x60000006a000>{number = 1, name = main}
2017-01-26 19:01:34.869 GCDNotes[93272:9727007] 2 = <NSThread: 0x60000006a000>{number = 1, name = main}
2017-01-26 19:01:34.869 GCDNotes[93272:9727007] 3 = <NSThread: 0x60000006a000>{number = 1, name = main}
2017-01-26 19:01:34.869 GCDNotes[93272:9727007] 4
*/
}
//并发 异步
- (void)sky_asyncConCurrent
{
//开启新线程. 有多少任务开启多少线程.(如果任务完成,其它的任务就会使用已经完成的.没有完成就会继续开启线程)
dispatch_queue_t sky_async_concurrent_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"4 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"5 = %@",[NSThread currentThread]);
});
dispatch_async(sky_async_concurrent_queue, ^{
NSLog(@"6 = %@",[NSThread currentThread]);
});
NSLog(@"7");
/*
运行结果:
2017-01-26 19:07:04.372 GCDNotes[93315:9751108] 3 = <NSThread: 0x6000002638c0>{number = 5, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751058] 7
2017-01-26 19:07:04.372 GCDNotes[93315:9751129] 2 = <NSThread: 0x60000007a600>{number = 4, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751107] 1 = <NSThread: 0x608000074940>{number = 3, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751110] 4 = <NSThread: 0x600000264300>{number = 6, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751394] 5 = <NSThread: 0x600000264380>{number = 7, name = (null)}
2017-01-26 19:07:04.372 GCDNotes[93315:9751108] 6 = <NSThread: 0x6000002638c0>{number = 5, name = (null)}
*/
}
dispatch_barrier_async栅栏函数
barrier_async
只有前面的任务执行并完成后才会执行. 如果后面还有任务要等到barrier_async
执行完成之后才会继续执行.
- 必须使用自定义的队列.否则即使加上也和全局队列效果一样,也就是在全局队列中
barrier_async
会和其它普通的任务一样.不会等到他之前的所有任务执行完成之后再执行. -
dispatch_barrier_sync
会在主线程中执行任务。
dispatch_queue_t sky_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(sky_queue, ^{
NSLog(@"1 = %@",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"2 = %@",[NSThread currentThread]);
});
dispatch_barrier_async(sky_queue, ^{
NSLog(@"barrier %@ ",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"3 = %@",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"4 = %@",[NSThread currentThread]);
});
dispatch_async(sky_queue, ^{
NSLog(@"5 = %@",[NSThread currentThread]);
});
/**
运行结果:
2017-01-26 19:42:09.012 GCDNotes[93364:9892792] 2 = <NSThread: 0x60800026d1c0>{number = 4, name = (null)}
2017-01-26 19:42:09.012 GCDNotes[93364:9892793] 1 = <NSThread: 0x600000076b40>{number = 3, name = (null)}
2017-01-26 19:42:09.012 GCDNotes[93364:9892793] barrier <NSThread: 0x600000076b40>{number = 3, name = (null)}
2017-01-26 19:42:09.013 GCDNotes[93364:9892792] 3 = <NSThread: 0x60800026d1c0>{number = 4, name = (null)}
2017-01-26 19:42:09.013 GCDNotes[93364:9892795] 4 = <NSThread: 0x608000276180>{number = 5, name = (null)}
2017-01-26 19:42:09.013 GCDNotes[93364:9892793] 5 = <NSThread: 0x600000076b40>{number = 3, name = (null)}
*/
dispatch_group_t队列组
- 使用场景: 异步执行2个操作,等都执行完成之后,回到主线程继续执行.(比如俩图片下载完后合并成一个,或俩网络请求完成之后去刷新列表等~(栅栏函数也可以 ))
- 设置阻塞组(超时)任务一定时间.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
第一个参数是需要阻塞的组,第二个是阻塞的时间. -
dispatch_group_notify
当group关联的block执行完毕后,就调用这个block
系统管理队列组和手动管理队列组. 两个是等价的. 手动的enter和leave必须成对使用.
//系统管理
dispatch_group_async(group, queue, ^{
//...
});
//手动管理
dispatch_group_enter(group);
dispatch_async(queue, ^{
// ...
dispatch_group_leave(group);
});
//线程队列组
dispatch_queue_t sky_queue = dispatch_queue_create("com.sky.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t sky_group = dispatch_group_create();
//线程组的用法和原理:可以给一个任务组设置一个阻塞(超时)时间,如
果给一个任务组的前两个任务设置一个超时时间那么如果这前两个任务
提前执行结束了,也就是在设定的阻塞时间内完成了任务,那么这个阻塞
就会放行下面的任务去执行任务,如果超过了超时时间那么这个阻塞也
会放行,让下面的任务可以执行.阻塞时间并不能杀死任务,也就是说如果
你设定了阻塞(超时)时间,阻塞时间前的任务在阻塞放行前并没有执行完
成,然后任务就被放行了,系统继续往下执行,但是这时候阻塞时间前的任
务也会执行完成如果你不手动杀死它的话.
//dispatch_group_wait(sky_group, 3); //第一个参数 阻塞的组 第二个
参数 阻塞时间
dispatch_group_async(sky_group, sky_queue, ^{
NSLog(@" 1 %@ ",[NSThread currentThread]);
});
dispatch_group_async(sky_group, sky_queue, ^{
NSLog(@" 2 %@ ",[NSThread currentThread]);
});
dispatch_group_async(sky_group, sky_queue, ^{
[NSThread sleepForTimeInterval:3];//等待三秒
NSLog(@" 3 %@ ",[NSThread currentThread]);
});
dispatch_group_async(sky_group, sky_queue, ^{
NSLog(@" 4 %@ ",[NSThread currentThread]);
});
//回到主线程
dispatch_group_notify(sky_group, sky_queue, ^{
//监测方法在最后一个任务执行的线程中执行
NSLog(@"回到主线程了 ~ %@" , [NSThread currentThread]);
});
/**
运行结果:
2017-01-26 20:26:27.768 GCDNotes[93449:10087233] 1 <NSThread: 0x60000026b6c0>{number = 4, name = (null)}
2017-01-26 20:26:27.768 GCDNotes[93449:10087235] 2 <NSThread: 0x600000260f40>{number = 3, name = (null)}
2017-01-26 20:26:27.768 GCDNotes[93449:10087330] 4 <NSThread: 0x600000271cc0>{number = 5, name = (null)}
2017-01-26 20:26:30.769 GCDNotes[93449:10087232] 3 <NSThread: 0x608000260f80>{number = 6, name = (null)}
2017-01-26 20:26:30.770 GCDNotes[93449:10087232] 回到主线程了 ~ <NSThread: 0x608000260f80>{number = 6, name = (null)}
*/
dispatch_semaphore_t信号量
信号量是用于控制并发数量的,所以只用在全局队列和并行队列中.
- 创建信号量,可以设置信号量的资源数。0表示没有资源,调用
dispatch_semaphore_wait
会立即等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- 等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- 通知信号,如果等待线程被唤醒则返回非0,否则返回0。
dispatch_semaphore_signal(semaphore);
//创建队列组
dispatch_group_t sky_group = dispatch_group_create();
//创建信号为3的信号量
dispatch_semaphore_t sky_semaphore = dispatch_semaphore_create(3);
dispatch_queue_t sky_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0 ; i < 10 ; i ++)
{
//设置等待信号 DISPATCH_TIME_FOREVER(永远) | DISPATCH_TIME_NOW(现在)
dispatch_semaphore_wait(sky_semaphore, DISPATCH_TIME_FOREVER);
//
dispatch_group_async(sky_group, sky_global, ^{
NSLog(@"i = %d : %@",i , [NSThread currentThread]);
[NSThread sleepForTimeInterval:1];
//设置通知信号
dispatch_semaphore_signal(sky_semaphore);
});
}
//通知主线程
dispatch_group_notify(sky_group, sky_global, ^{
NSLog(@"xxxxoooo");
});
/*
运行结果:
2017-01-26 21:10:53.006 GCDNotes[93525:10184012] i = 2 : <NSThread: 0x608000460fc0>{number = 5, name = (null)}
2017-01-26 21:10:53.006 GCDNotes[93525:10184037] i = 0 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:53.006 GCDNotes[93525:10184011] i = 1 : <NSThread: 0x608000279440>{number = 4, name = (null)}
2017-01-26 21:10:54.009 GCDNotes[93525:10184037] i = 4 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:54.009 GCDNotes[93525:10184012] i = 3 : <NSThread: 0x608000460fc0>{number = 5, name = (null)}
2017-01-26 21:10:54.009 GCDNotes[93525:10184011] i = 5 : <NSThread: 0x608000279440>{number = 4, name = (null)}
2017-01-26 21:10:55.015 GCDNotes[93525:10184037] i = 8 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:55.015 GCDNotes[93525:10184012] i = 6 : <NSThread: 0x608000460fc0>{number = 5, name = (null)}
2017-01-26 21:10:55.015 GCDNotes[93525:10184011] i = 7 : <NSThread: 0x608000279440>{number = 4, name = (null)}
2017-01-26 21:10:56.020 GCDNotes[93525:10184037] i = 9 : <NSThread: 0x60800026a040>{number = 3, name = (null)}
2017-01-26 21:10:57.023 GCDNotes[93525:10184037] xxxxoooo
*/
//结果分析: 来自: http://www.jianshu.com/p/ac11fe7ef78c
/*
首先这里面创建了一个组,这个组放在这里只是告诉你可以这么用,它不会影响信号量的功能.
这里创建了一个并发为3的信号量然而for循环是10个任务,那么理论
上10个任务会创建十个线程去执行,但是你信号量为3所以只能创建3
个线程去执行前面10个任务,然后等待前3个任务执行完成了腾出来新
的线程再去执行等待执行的.所以下面的线程 number 最大是4,当然这
前提是你设置的超时时间大于任务执行完的时间.如果设置超时时间是
0.1秒的话,任务等超时了还是会开启另外的线程去执行任务,这样就
达不到控制并发量的要求了.
*/
//理解信号量: 来自: http://www.jianshu.com/p/ac11fe7ef78c
/*
一般可以用停车来比喻:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此
时来了五辆车,那么就有一辆需要等待。信号量的值就相当于剩余车位
的数目,dispatch_semaphore_wait函数就相当于来了一辆车,
dispatch_semaphore_signal就相当于走了一辆车。停车位的剩余数目
在初始化的时候就已经指明了(dispatch_semaphore_create(long
value)),调用一次dispatch_semaphore_signal,剩余的车位就增加
一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩
余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等
待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己
设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就
开进去停车。而有些车主就像把车停在这,所以就一直等下去。
*/
dispatch_apply 快速迭代
可以给定指定的次数将block追加到指定的Dispatch Queue中,并且等待全部结束处理执行。
dispatch_queue_t sky_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
NSArray *array = @[@"北京",@"我爱你",@"济南大学的遗憾",@"在济南大学的操场望天空",@"济南大学的食堂",@"梦游济南大学"];
//参数1: 要遍历的次数
//参数2: 在那个线程中
//参数3: 回调
dispatch_apply(array.count, sky_queue, ^(size_t index) {
NSLog(@"%@",array[index]);
});
NSLog(@"xxxooo");
/*
运行结果:
2017-01-26 21:48:46.007 GCDNotes[93665:10365139] 在济南大学的操场望天空
2017-01-26 21:48:46.007 GCDNotes[93665:10365141] 我爱你
2017-01-26 21:48:46.007 GCDNotes[93665:10365138] 济南大学的遗憾
2017-01-26 21:48:46.007 GCDNotes[93665:10364896] 北京
2017-01-26 21:48:46.007 GCDNotes[93665:10365139] 济南大学的食堂
2017-01-26 21:48:46.007 GCDNotes[93665:10365141] 梦游济南大学
2017-01-26 21:48:46.007 GCDNotes[93665:10364896] xxxooo
*/
//无序.
dispatch_suspend和dispatch_resume
NSOperationQueue
有暂停(suspend)和恢复(resume)。其实GCD中的队列也有类似的功能。用法也非常简单:
dispatch_suspend(queue)
//暂停某个队列
dispatch_resume(queue)
//恢复某个队列
这些函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。
例:
- 多线程中下载图片,回到主线程刷新UI
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//多线程执行 ~
NSURL *imageUrl = [NSURL URLWithString:@"http://b.hiphotos.baidu.com/image/pic/item/6a63f6246b600c33fe5527171e4c510fd8f9a1c5.jpg"];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = [UIImage imageWithData:imageData];
/*
注意:
如果想等UI更新完毕再执行后面的代码, 那么使用同步函数
如果不想等UI更新完毕就需要执行后面的代码, 那么使用异步函数
*/
dispatch_sync(dispatch_get_main_queue(), ^{
//同步回到主线程刷新ui
self.imageView.image = image;
});
});
- GCD 延迟
//延迟1秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
//这里传入全局队列会在子线程中执行block,如果传入主队列就会在主线程中执行block
// ...
});
- 一次执行(一般用来实现单列和全局就调用一次代码的地方比如:时间计算那块.特别是在复杂的列表页面每次都去计算时间格式很消耗性能.加上这个滑动就会流畅很多.)
//一次执行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});