1. GCD的两个核心概念: 任务和队列
- 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。
在 GCD 中是放在 block 中的。 - 执行任务有两种方式:同步执行(sync)和异步执行(async)。
- 两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 创建串行队列:
dispatch_queue_t queue = dispatch_queue_create("自定义队列名", NULL);
//或
dispatch_queue_t queue = dispatch_queue_create("自定义队列名", DISPATCH_QUQUE_SERIAL);
- 创建并发队列:
dispatch_queue_t queue = dispatch_queue_create("自定队列名", DISPATCH_QUQUE_CONCURRENT);
- 同步执行(sync):
同步添加任务到 当前 队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
dispatch_sync(queue, ^{ ... });
- 异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
dispatch_async(queue, ^{ ... });
- 死锁: 因为把任务2添加到了主线程队列中,但是串行队列是顺序执行的, 所以任务二必须要等待当前任务执行完了以后才能执行任务2;但是当前任务就是执行这三个任务,所以当前任务无法执行完, 任务2也不会执行。
/// 以下代码在主线程执行
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
2.GCD 延时执行方法:dispatch_after
- (void)GCDAfter
{
NSLog(@"初始化完成");
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"两秒后执行这句代码--线程:%@",[NSThread currentThread]);
});
}
打印结果:
2018-04-01 15:28:30.183169+0800 初始化完成
2018-04-01 15:28:32.361869+0800 两秒后执行这句代码--线程:<NSThread: 0x60400006b7c0>{number = 1, name = main}
3. GCD 一次性代码(只执行一次):dispatch_once
- (void)GCDOnce
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只会执行一次这里的代码, 默认是线程安全的, 主要运用与单例的创建
});
}
4. GCD 快速迭代方法:dispatch_apply
- (void)GCDApply
{
NSLog(@"初始化完成");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"开始");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"index:%zd---线程:%@", index, [NSThread currentThread]);
});
NSLog(@"结束");
}
5. GCD 栅栏方法:dispatch_barrier_async
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。
这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。
这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。
下面代码演示:
- (void)GCDBarrier
{
NSLog(@"初始化完成");
dispatch_queue_t queue = dispatch_queue_create("testGCD.lianchen", DISPATCH_QUEUE_CONCURRENT);
// 第一个任务
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"执行第一个任务");
});
// 第二个任务
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"执行第二个任务");
});
// 执行 barrie 任务
dispatch_barrier_sync(queue, ^{
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"执行 barrie 任务");
});
// 第三个任务
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"执行第三个任务");
});
// 第四个任务
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"执行第四个任务");
});
}
执行结果:
2018-04-01 10:46:37.446851+0800 初始化完成
2018-04-01 10:46:39.447319+0800 执行第一个任务
2018-04-01 10:46:39.447321+0800 执行第二个任务
2018-04-01 10:46:41.448313+0800 执行 barrie 任务
2018-04-01 10:46:43.453644+0800 执行第四个任务
2018-04-01 10:46:43.453644+0800 执行第三个任务
6.GCD 的队列组:dispatch_group
6.1 dispatch_group_notify
- (void)GCDGroup_notify
{
NSLog(@"初始化完成");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行耗时操作1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行耗时操作2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"执行完上面的两个异步任务后, 才会执行这里");
});
}
运行结果:
2018-04-01 20:35:57.330323+0800 初始化完成
2018-04-01 20:35:59.335313+0800 执行耗时操作2
2018-04-01 20:35:59.335313+0800 执行耗时操作1
2018-04-01 20:35:59.335694+0800 执行完上面的两个异步任务后, 才会执行这里
6.2 dispatch_group_wait
- 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
- (void)GCDGroup_wait
{
NSLog(@"初始化完成");
for (int i = 0; i < 10; i++) {
NSLog(@"打印顺序:%d", i);
if (i == 5) {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行耗时操作1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行耗时操作2");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"执行完上面的两个异步任务后, 才会往下执行");
}
}
}
6.3dispatch_group enter , leave
- dispatch_group_enter 标志着一个任务追加到 group, 相当于group中未执行的任务数量 +1
- dispatch_group_leave 标志着一个任务追加到 group, 相当于group中未执行的任务数量 -1
- 当 group 中未执行完毕任务数为0的时候
才会使dispatch_group_wait解除阻塞
以及执行追加到dispatch_group_notify中的任务 - 所以 enter与leave可以配合dispatch_group_wait与dispatch_group_notify使用, 两个的区别是一个会阻塞主线程, 一个不会阻塞主线程.
- (void)GCDGroup_enter_leave
{
NSLog(@"初始化完成");
for (int i = 0; i < 10; i++) {
NSLog(@"打印顺序:%d", i);
if (i == 5) {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行耗时操作1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"执行耗时操作2");
dispatch_group_leave(group);
});
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"执行完上面的两个异步任务后, 才会执行这里, 但是这里不会阻塞主线程");
// });
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"执行完上面的两个异步任务后, 才会往下执行, 这里会阻塞主线程");
}
}
}
打印结果:
2018-04-01 21:04:08.115499+0800 初始化完成
2018-04-01 21:04:08.115753+0800 打印顺序:0
2018-04-01 21:04:08.115892+0800 打印顺序:1
2018-04-01 21:04:08.115997+0800 打印顺序:2
2018-04-01 21:04:08.116150+0800 打印顺序:3
2018-04-01 21:04:08.116320+0800 打印顺序:4
2018-04-01 21:04:08.116484+0800 打印顺序:5
2018-04-01 21:04:10.117688+0800 执行耗时操作2
2018-04-01 21:04:10.117729+0800 执行耗时操作1
2018-04-01 21:04:10.118392+0800 执行完上面的两个异步任务后, 才会往下执行, 这里会阻塞主线程
2018-04-01 21:04:10.118589+0800 打印顺序:6
2018-04-01 21:04:10.118866+0800 打印顺序:7
2018-04-01 21:04:10.119055+0800 打印顺序:8
2018-04-01 21:04:10.119220+0800 打印顺序:9
6.4 GCD 信号量:dispatch_semaphore
6.4.1 semaphore的简单使用:
- 在 Dispatch Semaphore 中,使用计数来完成这个功能, 计数为0时等待,不可通过。
- 计数为1或大于1时,计数减1且不等待,可通过。
- Semaphore 提供三个函数:
1. dispatch_semaphore_t semaphore = dispatch_semaphore_create(long value);
创建一个Semaphore并初始化信号的总量
2. long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
发送一个信号, 使信号总量加 1
3. long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
接收一个信号和时间值, 若信号的信号量为0, 则会阻塞主当前线程, 知道信号量大于0, 程序才会往下执行, 同时信号总数 会 -1
看代码:
- (void)GCDSemaphore
{
NSLog(@"初始化完成");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int a = 0;
dispatch_async(queue, ^{
a = 99;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"a = %d", a);
}
结果输出为 a = 99;
6.4.2 Dispatch Semaphore线程安全(为线程加锁):
先看代码:
- (void)GCDSemaphoreLock
{
NSLog(@"初始化完成");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 100; i++) {
dispatch_async(queue, ^{
// 相当于加锁
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[NSThread sleepForTimeInterval:1];
/// 这里执行耗时操作
NSLog(@"这里执行耗时操作");
// 相当于解锁
dispatch_semaphore_signal(semaphore);
});
}
}
- 当线程1执行到dispatch_semaphore_wait这一行时,semaphore的信号量为1,所以使信号量-1变为0,并且线程1继续往下执行;
如果当在线程1的耗时操作还没执行完的时候,又有线程2来访问,执行dispatch_semaphore_wait时由于此时信号量为0,且时间为DISPATCH_TIME_FOREVER,所以会一直阻塞线程2(此时线程2处于等待状态),直到线程1执行完耗时操作并执行完dispatch_semaphore_signal使信号量为1后,信号总量为0后, 线程2才能解除阻塞继续住下执行。以上可以保证同时只有一个线程执行耗时操作这代码。 - 再举个栗子: 关于售票
三个地方同时售一趟车的票
- (void)GCDSemaphoreLock
{
NSLog(@"初始化完成: 买票示例");
self.ticketCount = 50;
/// queue1 代表北京售票点
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// queue2 代表上海售票点
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// queue3 代表广州售票点
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf buyTicketSafe:semaphore];
});
dispatch_async(queue2, ^{
[weakSelf buyTicketSafe:semaphore];
});
dispatch_async(queue3, ^{
[weakSelf buyTicketSafe:semaphore];
});
}
/// 线程安全买票
- (void)buyTicketSafe:(dispatch_semaphore_t)semaphore
{
// 相当于加锁
while (1) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount -= 1;
NSLog(@"剩余票数:%ld, 当前线程:%@", self.ticketCount, [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.3];
}else {
// 售完
NSLog(@"票已售罄!");
// 相当于解锁
dispatch_semaphore_signal(semaphore);
break;
}
// 没执行一次解锁一次
// 相当于解锁
dispatch_semaphore_signal(semaphore);
}
}
看输出结果:
2018-04-01 22:29:16.542819+0800 初始化完成: 买票示例
2018-04-01 22:29:16.543259+0800 剩余票数:49, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:16.845560+0800 剩余票数:48, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:17.148390+0800 剩余票数:47, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:17.453090+0800 剩余票数:46, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:17.754377+0800 剩余票数:45, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:18.055701+0800 剩余票数:44, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:18.357465+0800 剩余票数:43, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
..........
2018-04-01 22:29:29.290197+0800 剩余票数:7, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:29.594207+0800 剩余票数:6, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:29.895484+0800 剩余票数:5, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:30.197834+0800 剩余票数:4, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:30.498786+0800 剩余票数:3, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:30.802413+0800 剩余票数:2, 当前线程:<NSThread: 0x600000465140>{number = 5, name = (null)}
2018-04-01 22:29:31.103086+0800 剩余票数:1, 当前线程:<NSThread: 0x60400027a5c0>{number = 3, name = (null)}
2018-04-01 22:29:31.404222+0800 剩余票数:0, 当前线程:<NSThread: 0x600000464ac0>{number = 4, name = (null)}
2018-04-01 22:29:31.708613+0800 票已售罄!
2018-04-01 22:29:31.709075+0800 票已售罄!
2018-04-01 22:29:31.709347+0800 票已售罄!