什么是GCD呢? 咱们先来看看百度百科的解释简单了解下概念:html
引自百度百科:
Grand Central Dispatch(GCD): 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其余对称多处理系统。 它是一个在线程池模式的基础上执行的并发任务。程序员
为何要用GCD呢?
由于GCD有不少好处, 具体以下:编程
既然GCD有这么多的好处, 那么下面咱们就来系统的学习一下GCD的使用方法:安全
学习GCD以前, 先来了解GCD中两个核心的概念: 任务和队列。bash
任务: 就是执行操做的意思, 换句话说就是你的线程中执行的那段代码。 在GCD中是放在block中的。 执行任务有两种方式:同步执行(sync)和异步执行(async)。
二者的主要区别是:是否等待队列的任务执行结束, 以及是否具有开启新线程的能力。session
同步执行(sync):多线程
异步执行(async):并发
举个简单的例子: 你要打电话给小明和小白。app
同步执行就是,你打电话给小明的时候,不能同时打给小白,等到给小明打完了,才能打给小白(等待任务执行结束)。 并且只能用当前的电话(不具有开启新线程的能力)。异步
而异步执行就是, 你打电话给小明的时候,不等和小明通话结束,还能直接给小白打电话,不用等着和小明通话结束再打(不用等待任务执行结束)。除了当前电话,你还可使用其余所能使用的电话(具有开启新线程的能力)。
注意: 异步执行(async)虽然具备开启新线程的能力,可是并不必定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。
队列(Dispatch Queue): 这里的队列指执行任务的等待队列, 即用来存听任务的队列。 队列是一种特殊的线性表, 采用FIFO(先进先出)的原则,即新任务老是被插入到队列的末尾, 而读取任务的时候老是从队列的头部开始读取。 每读取一个任务, 则从队列中释放一个任务。队列的结构可参考下图:
注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
二者具体区别以下两图所示:
GCD的使用步骤其实很简单, 只有两步:
1:建立一个队列(串行队列或并发队列)
2: 将任务追加到任务的等待队列中,而后系统就会根据任务类型执行任务(同步执行或异步执行)
下边来看看队列的建立方法/获取方法, 以及任务的建立方法。
//串行队列的建立方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
//并发队列的建立方法
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
复制代码
//主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
复制代码
//全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
复制代码
GCD提供了同步执行任务的建立方法dispatch_sync和异步执行任务建立方法dispatch_async。
//同步执行任务建立方法
dispatch_sync(queue, ^{
//这里放同步执行任务代码
});
//异步执行任务建立方法
dispatch_async(queue, ^{
//这里放异步执行任务代码
});
复制代码
虽然使用GCD只需两步, 可是既然咱们有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么咱们就有了四种不一样的组合方式。这四种不一样的组合方式是:
1: 同步执行 + 并发队列
2:异步执行 + 并发队列
3:同步执行 + 串行队列
4:异步执行 + 串行队列
复制代码
实际上,刚才还说了两种特殊队列: 全局并发队列、主队列。 全局并发队列能够做为普通并发队列来使用。可是主队列由于有点特殊, 因此咱们就又多了两种组合方式。 这样就有六种不一样的组合方式了。
5: 同步执行 + 主队列
6:异步执行 + 主队列
复制代码
那么这几种不一样组合方式各有什么区别呢?这里为了方便, 先上结果, 再来说解。
先来说讲并发队列的两种执行方式
/**
* 同步执行 + 并发队列
* 特色: 在当前线程中执行任务, 不会开启新线程, 执行完一个任务, 再执行下一个任务
*/
- (void)syncConcurrent {
//打印当前线程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread);//打印当前线程
}
});
dispatch_sync(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread);//打印当前线程
}
});
dispatch_sync(queue, ^{
//追加任务3
for (int i = 0; i < 2 ; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@", NSThread.currentThread);//打印当前线程
}
});
NSLog(@"syncConcurrent -- end");
}
输出:
currentThread = <NSThread: 0x600003aca940>{number = 1, name = main}
syncConcurrent---begin
1---<NSThread: 0x600003aca940>{number = 1, name = main}
1---<NSThread: 0x600003aca940>{number = 1, name = main}
2---<NSThread: 0x600003aca940>{number = 1, name = main}
2---<NSThread: 0x600003aca940>{number = 1, name = main}
3---<NSThread: 0x600003aca940>{number = 1, name = main}
3---<NSThread: 0x600003aca940>{number = 1, name = main}
syncConcurrent -- end
复制代码
从同步执行+并发队列中可看到:
/**
* 异步执行 + 并发队列
* 特色: 能够开启多个线程, 任务交替(同时)执行
*/
- (void)asyncConcurrent {
//打印当前线程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务3
for (int i = 0; i < 2 ; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@", NSThread.currentThread); //打印当前线程
}
});
NSLog(@"asyncConcurrent -- end");
}
输出结果:
currentThread = <NSThread: 0x600000f494c0>{number = 1, name = main}
asyncConcurrent---begin
asyncConcurrent -- end
3---<NSThread: 0x600000fda980>{number = 3, name = (null)}
2---<NSThread: 0x600000fdf700>{number = 5, name = (null)}
1---<NSThread: 0x600000fc5ec0>{number = 4, name = (null)}
3---<NSThread: 0x600000fda980>{number = 3, name = (null)}
2---<NSThread: 0x600000fdf700>{number = 5, name = (null)}
1---<NSThread: 0x600000fc5ec0>{number = 4, name = (null)}
复制代码
在并发队列+异步执行中能够看出:
/**
* 同步执行 + 串行队列
* 特色: 不会开启新线程, 在当前线程执行任务. 任务是串行的, 执行完一个任务, 再执行下一个任务.
*/
- (void)syncSerial {
//打印当前线程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_sync(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_sync(queue, ^{
//追加任务3
for (int i = 0; i < 2 ; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@", NSThread.currentThread); //打印当前线程
}
});
NSLog(@"syncSerial -- end");
}
输出结果:
currentThread = <NSThread: 0x6000017394c0>{number = 1, name = main}
syncSerial---begin
1---<NSThread: 0x6000017394c0>{number = 1, name = main}
1---<NSThread: 0x6000017394c0>{number = 1, name = main}
2---<NSThread: 0x6000017394c0>{number = 1, name = main}
2---<NSThread: 0x6000017394c0>{number = 1, name = main}
3---<NSThread: 0x6000017394c0>{number = 1, name = main}
3---<NSThread: 0x6000017394c0>{number = 1, name = main}
syncSerial -- end
复制代码
从同步执行 + 串行队列能够看出:
/**
* 异步执行 + 串行队列
* 特色: 会开启新线程, 可是由于任务是串行的, 执行完一个任务, 再执行下一个任务
*/
- (void)asyncSerial {
//打印当前线程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务3
for (int i = 0; i < 2 ; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@", NSThread.currentThread); //打印当前线程
}
});
NSLog(@"asyncSerial---end");
}
输出结果:
currentThread = <NSThread: 0x60000357e8c0>{number = 1, name = main}
asyncSerial---begin
asyncSerial---end
1---<NSThread: 0x600003573540>{number = 3, name = (null)}
1---<NSThread: 0x600003573540>{number = 3, name = (null)}
2---<NSThread: 0x600003573540>{number = 3, name = (null)}
2---<NSThread: 0x600003573540>{number = 3, name = (null)}
3---<NSThread: 0x600003573540>{number = 3, name = (null)}
3---<NSThread: 0x600003573540>{number = 3, name = (null)}
复制代码
在异步执行 + 串行队列中能够看到:
下面讲讲刚才咱们提到过的特殊队列: 主队列
咱们再来看看主队列的两种组合方式.
/**
* 同步执行 + 主队列
* 特色(主线程调用):互等卡住不执行
* 特色(其余线程调用): 不会开启新线程, 执行完一个任务, 再执行下一个任务
*/
- (void)syncMain {
//打印当前线程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_sync(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_sync(queue, ^{
//追加任务3
for (int i = 0; i < 2 ; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@", NSThread.currentThread); //打印当前线程
}
});
NSLog(@"syncMain---end");
}
输出结果:
currentThread = <NSThread: 0x6000031c28c0>{number = 1, name = main}
syncMain---begin
(lldb)
复制代码
在同步执行 + 主队列中能够发现:
这是由于咱们在主线程中执行syncMain方法,至关于把syncMain任务放到了主线程的队列中. 而同步执行会等待当前队列中的任务执行完毕, 才会接着执行. 那么当咱们把任务1追加到主队列中, 任务1就在等待主线程处理完syncMain任务. 而syncMain任务须要等待任务1执行完毕, 才能接着执行.
那么, 如今的状况就是syncMain任务和任务1都在等对方执行完毕. 这样你们互相等待, 因此就卡住了, 因此咱们的任务执行不了, 并且syncMain---end也没有打印.
若是不在主线程中调用, 而在其余线程中调用会如何呢?
// 使用 NSThread 的 detachNewThreadSelector 方法会建立线程, 并自动启动线程执行selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
输出结果:
currentThread = <NSThread: 0x600003fe09c0>{number = 3, name = (null)}
syncMain---begin
1---<NSThread: 0x600003f65480>{number = 1, name = main}
1---<NSThread: 0x600003f65480>{number = 1, name = main}
2---<NSThread: 0x600003f65480>{number = 1, name = main}
2---<NSThread: 0x600003f65480>{number = 1, name = main}
3---<NSThread: 0x600003f65480>{number = 1, name = main}
3---<NSThread: 0x600003f65480>{number = 1, name = main}
syncMain---end
复制代码
在其余线程中使用同步执行 + 主队列可看到:
为何如今就不会卡住了呢? 由于syncMain任务放到了其余线程里, 而任务1, 任务2, 任务3都追加到了主队了中, 这三个任务都会在主线程中执行. syncMain任务在其余线程中执行到追加任务1到主队列中, 由于主队列如今没有正在执行的任务, 因此, 会直接执行主队列的任务1, 等任务1执行完毕, 再接着执行任务2, 任务3. 因此这里不会卡住线程.
/**
* 异步执行 + 主队列
* 特色: 只在主线程中执行任务, 执行完一个任务, 再执行下一个任务
*/
- (void)asyncMain {
//打印当前线程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务3
for (int i = 0; i < 2 ; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@", NSThread.currentThread); //打印当前线程
}
});
NSLog(@"asyncMain---end");
}
输出结果:
currentThread = <NSThread: 0x6000017dd4c0>{number = 1, name = main}
asyncMain---begin
asyncMain---end
1---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
1---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
2---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
2---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
3---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
3---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
复制代码
在异步执行 + 主队列中能够看到:
在iOS开发过程当中, 咱们通常在主线程里边进行UI刷新, 例如: 点击, 滚动, 拖拽等事件. 咱们一般把一些耗时的操做放在其余线程, 好比说图片下载, 文件上传等耗时操做, 而当咱们有时候在其余线程完成了耗时操做时, 须要回到主线程, 那么就用到了线程之间的通信.
/**
* 线程间通讯
*/
- (void)communication {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印当前线程
//获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
//异步追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(mainQueue, ^{
//回到主线程
[NSThread sleepForTimeInterval:4]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
});
}
输出结果:
currentThread---<NSThread: 0x60000137c980>{number = 1, name = main}
不符合标砖
1---<NSThread: 0x6000013e4d00>{number = 3, name = (null)}
1---<NSThread: 0x6000013e4d00>{number = 3, name = (null)}
2---<NSThread: 0x60000137c980>{number = 1, name = main}
复制代码
/**
* 栅栏方法: dispatch_barrier_async
*/
- (void)barrier {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印当前线程
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@", NSThread.currentThread); //打印当前线程
}
});
dispatch_barrier_async(queue, ^{
//追加任务 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"barrier---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_async(queue, ^{
//追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@",NSThread.currentThread);
}
});
dispatch_async(queue, ^{
//追加任务4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"4---%@",NSThread.currentThread);
}
});
}
输出结果:
currentThread---<NSThread: 0x6000002ec080>{number = 1, name = main}
2---<NSThread: 0x60000028ba00>{number = 4, name = (null)}
1---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
1---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
2---<NSThread: 0x60000028ba00>{number = 4, name = (null)}
barrier---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
barrier---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
3---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
4---<NSThread: 0x600000283f80>{number = 5, name = (null)}
4---<NSThread: 0x600000283f80>{number = 5, name = (null)}
3---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
复制代码
在dispatch_barrier_async执行结果中能够看出:
咱们常常会遇到这样的需求: 在指定时间(例如3秒)以后执行某个任务. 能够用GCD的dispatch_after函数来实现.
须要注意的是: dispatch_after函数并非在指定时间以后才开始执行处理, 而是在指定时间以后将任务追加到主队列中. 严格来讲, 这个时间并非绝对准确的, 但要想大体延迟执行任务, dispatch_after函数是颇有效的.
/**
* 延时执行任务: dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印当前线程
NSLog(@"after---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//2.0 秒后异步追加任务代码到主队列, 并开始执行
NSLog(@"after---%@",NSThread.currentThread); //打印当前线程
});
}
输出结果:
17:51:02.998177+0800 currentThread---<NSThread: 0x600002cacd40>{number = 1, name = main}
17:51:02.998384+0800 after---begin
17:51:04.998680+0800 after---<NSThread: 0x600002cacd40>{number = 1, name = main}
复制代码
能够看出: 在打印after---begin以后大约2.0秒的时间, 打印了after---<NSThread: 0x600002cacd40>{number = 1, name = main}
/**
* 一次性代码(只执行一次): dispatch_once
*/
- (void)once {
for (int i = 0; i < 10; i++) {
NSLog(@"第%d次执行GCD一次性代码",i);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只执行1次的代码(这里面默认是线程安全的)
NSLog(@"只执行1次的代码");
});
}
}
输出结果:
第0次执行GCD一次性代码
只执行1次的代码
第1次执行GCD一次性代码
第2次执行GCD一次性代码
第3次执行GCD一次性代码
第4次执行GCD一次性代码
第5次执行GCD一次性代码
第6次执行GCD一次性代码
第7次执行GCD一次性代码
第8次执行GCD一次性代码
第9次执行GCD一次性代码
复制代码
从输出结果能够看出, 不管执行多少次dispatch_once,最终都只会执行一次;
/**
快速迭代方法 dispatch_apply
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, NSThread.currentThread);
});
NSLog(@"apply-end");
}
输出结果:
apply---begin
2---<NSThread: 0x60000198ccc0>{number = 5, name = (null)}
4---<NSThread: 0x600001981000>{number = 6, name = (null)}
3---<NSThread: 0x6000019f14c0>{number = 1, name = main}
0---<NSThread: 0x6000019ab380>{number = 3, name = (null)}
1---<NSThread: 0x60000198a080>{number = 4, name = (null)}
5---<NSThread: 0x6000019ab4c0>{number = 7, name = (null)}
apply-end
复制代码
由于是在并发队列中异步执行任务, 因此各个任务的执行时间长短不定, 最后结束顺序也不定. 可是apply-end必定在最后执行. 这是由于dispatch_apply函数会等待所有任务执行完毕.
有时候咱们会有这样的需求: 分别异步执行2个耗时任务, 而后当2个耗时任务都执行完毕后再回到主线程执行任务. 这时候咱们能够用到GCD的队列组.
/**
队列组 dispatch_group_notify
*/
- (void)groupNotify {
//打印当前线程
NSLog(@"currentThread---%@",NSThread.currentThread);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//追加任务1
for (int i = 0 ; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",[NSThread currentThread]); //打印当前线程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//等前面的异步任务1, 任务2都执行完毕后, 回到主线程执行下边的任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@",[NSThread currentThread]); //打印当前线程
}
NSLog(@"group---end");
});
}
输出结果:
currentThread---<NSThread: 0x60000202e8c0>{number = 1, name = main}
group---begin
2---<NSThread: 0x6000020a9040>{number = 4, name = (null)}
1---<NSThread: 0x6000020a89c0>{number = 3, name = (null)}
2---<NSThread: 0x6000020a9040>{number = 4, name = (null)}
1---<NSThread: 0x6000020a89c0>{number = 3, name = (null)}
3---<NSThread: 0x60000202e8c0>{number = 1, name = main}
3---<NSThread: 0x60000202e8c0>{number = 1, name = main}
group---end
复制代码
从dispatch_group_notify相关代码运行输出结果能够看出:
当全部任务都执行完成以后, 才执行dispatch_group_notify block中的任务.
/**
队列组 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@", NSThread.currentThread);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
});
// 等待上面的任务所有完成后, 会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@",NSThread.currentThread); //打印当前线程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"4---%@",NSThread.currentThread); //打印当前线程
}
});
}
输出结果:
currentThread---<NSThread: 0x6000018e94c0>{number = 1, name = main}
group---begin
2---<NSThread: 0x600001895b80>{number = 3, name = (null)}
1---<NSThread: 0x60000186d700>{number = 4, name = (null)}
1---<NSThread: 0x60000186d700>{number = 4, name = (null)}
2---<NSThread: 0x600001895b80>{number = 3, name = (null)}
group---end
4---<NSThread: 0x60000186d700>{number = 4, name = (null)}
3---<NSThread: 0x600001895b80>{number = 3, name = (null)}
3---<NSThread: 0x600001895b80>{number = 3, name = (null)}
4---<NSThread: 0x60000186d700>{number = 4, name = (null)}
复制代码
从dispatch_group_wait相关代码运行输出结果能够看出:
/**
队列组 dispatch_group_enter, dispatch_group_leave
*/
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread); //打印当前线程
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"2---%@",NSThread.currentThread); //打印当前线程
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操做都执行完毕后, 回到主线程
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"3---%@",NSThread.currentThread);
}
NSLog(@"group-end");
});
}
输出结果:
currentThread---<NSThread: 0x600000326c80>{number = 1, name = main}
group---begin
1---<NSThread: 0x60000035ae40>{number = 4, name = (null)}
2---<NSThread: 0x6000003a1840>{number = 3, name = (null)}
1---<NSThread: 0x60000035ae40>{number = 4, name = (null)}
2---<NSThread: 0x6000003a1840>{number = 3, name = (null)}
3---<NSThread: 0x600000326c80>{number = 1, name = main}
3---<NSThread: 0x600000326c80>{number = 1, name = main}
group-end
复制代码
从dispatch_group_enter, dispatch_group_leave相关代码运行结果中能够看出: 当全部任务执行完成以后,才执行dispatch_group_notify中的任务. 这里的dispatch_group_enter, dispatch_group_leave组合, 其实等同于dispatch_group_async.
GCD中的信号量是指Dispatch Semaphore, 是持有计数的信号. 相似于太高速路收费站的栏杆. 能够经过时, 打开栏杆, 不能够经过时, 关闭栏杆. 在Dispatch Semaphore中, 使用计数来完成这个功能, 计数小于0时等待, 不可经过.计数为0或大于0时, 计数减1且不等待, 可经过.
Dispatch Semaphore 提供了三个函数
注意: 信号量的使用前提是: 想清楚你须要处理哪一个线程等待(阻塞), 又要哪一个线程继续执行, 而后使用信号量.
Dispatch Semaphore 在实际开发中主要用于:
咱们在开发中, 会遇到这样的需求: 异步执行耗时任务, 并使用异步执行的结果进行一些额外的操做. 换句话说, 至关于, 将异步执行任务转换为同步执行任务. 好比说: AFNetworking中AFURLSessionManager.m里面的tasksForKeyPath: 方法. 经过引入信号量的方式, 等待异步执行任务结果, 获取到tasks, 而后再返回该tasks.
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
复制代码
下面, 咱们来利用Dispatch Semaphore实现线程同步
/**
semaphore 线程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",NSThread.currentThread); //打印当前线程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//semaphore初始建立时计数为0
__block int number = 0;
dispatch_async(queue, ^{
// 追加任务1
[NSThread sleepForTimeInterval:2]; //模拟耗时操做
NSLog(@"1---%@",NSThread.currentThread);
number = 100;
dispatch_semaphore_signal(semaphore);//总信号量+1, 此时semaphore == 0, 正在被阻塞的线程(主线程)恢复继续执行
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //semaphore减1, 此时semaphore == -1, 当前线程进入等待状态
NSLog(@"semaphore---end, number = %zd",(long)number);
}
输出结果:
currentThread---<NSThread: 0x600001d394c0>{number = 1, name = main}
semaphore---begin
1---<NSThread: 0x600001d46480>{number = 3, name = (null)}
semaphore---end, number = 100
复制代码
从Dispatch Semaphore实现线程同步的代码能够看到:
这样就实现了线程同步, 将异步执行任务转换为同步执行任务.
线程安全: 若是你的代码所在的进程中有多个线程在同时运行, 而这些线程可能会同时运行这段代码. 若是每次运行结果和单线程运行的结果是同样的, 并且其余的变量的值也和预期的是同样的, 就是线程安全的.
若每一个线程中对全局变量、静态变量只有读操做, 而无写操做, 通常来讲, 这个全局变量是线程安全的; 如有多个线程同时执行写操做(更改变量), 通常都须要考虑线程同步, 不然的话就可能影响线程安全。
线程同步: 可理解为线程A和线程B一块配合, A执行到必定程度时要依靠线程B的某个结果, 因而停下来, 示意B运行; B依言执行,再将结果给A; A再继续操做。
举个简单的例子就是: 两我的在一块儿聊天。两我的不能同时说话, 避免听不清(操做冲突)。等一我的说完(一个线程结束操做), 另外一个再说(另外一个线程再开始操做)。
下面,咱们模拟火车票售卖的方式, 实现NSThread线程安全和解决线程同步问题。
场景: 总共有50张火车票, 有两个售卖火车票的窗口, 一个是北京火车票售卖窗口, 另外一个是上海火车票售卖窗口。 两个窗口同时售卖火车票, 卖完为止。
先来看看不考虑线程安全的代码:
/**
* 非线程安全: 不使用 semaphore
* 初始化火车票数量, 卖票窗口(非线程安全), 并开始卖票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",NSThread.currentThread); //打印当前线程
NSLog(@"semaphore--begin");
self.ticketSurplusCount = 50;
//queue1 表明北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
//queue2 表明上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof (self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
售卖火车票(非线程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { //若是还有票, 继续售票
self.ticketSurplusCount--;
NSLog(@"剩余票数: %d 窗口: %@",self.ticketSurplusCount, NSThread.currentThread);
[NSThread sleepForTimeInterval:0.2];
}
else { //若是已卖完, 关闭售票窗口
NSLog(@"全部火车票均已售完");
break;
}
}
}
输出结果:
currentThread---<NSThread: 0x60000083cf80>{number = 1, name = main}
semaphore--begin
剩余票数: 48 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 49 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 46 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 47 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 45 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
...
剩余票数: 5 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 4 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 3 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 1 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 2 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
全部火车票均已售完
剩余票数: 0 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
全部火车票均已售完
复制代码
能够看到在不考虑线程安全, 不适用semaphore的状况下, 获得的票数是错乱的, 这样显然不符合咱们的需求, 因此咱们须要考虑线程安全的问题。
考虑线程安全的代码:
/**
* 线程安全: 使用 semaphore 加锁
* 初始化火车票数量, 卖票窗口(线程安全), 并开始卖票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",NSThread.currentThread); //打印当前线程
NSLog(@"semaphore--begin");
self.semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
//queue1 表明北京火车票售卖窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
//queue2 表明上海火车票售卖窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof (self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
售卖火车票(线程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
//至关于加锁
dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { //若是还有票, 继续售票
self.ticketSurplusCount--;
NSLog(@"剩余票数: %d 窗口: %@",self.ticketSurplusCount, NSThread.currentThread);
[NSThread sleepForTimeInterval:0.2];
}
else { //若是已卖完, 关闭售票窗口
NSLog(@"全部火车票均已售完");
//至关于解锁
dispatch_semaphore_signal(self.semaphoreLock);
break;
}
//至关于解锁
dispatch_semaphore_signal(self.semaphoreLock);
}
}
输出结果:
currentThread---<NSThread: 0x60000317e8c0>{number = 1, name = main}
semaphore--begin
剩余票数: 49 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 48 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 47 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 46 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 45 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
...
剩余票数: 5 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 4 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 3 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 2 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 1 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 0 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
全部火车票均已售完
全部火车票均已售完
复制代码
能够看出, 在考虑了线程安全的状况下, 使用dispatch_semaphore机制以后, 获得的票数是正确的, 没有出现混乱的状况。 咱们也就解决了多个线程同步的问题。