# GCD优点
1.GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
2.GCD更接近底层,性能较高
3.GCD 会自动利用更多的 CPU 内核(比如双核、四核),GCD 可用于多核的并行运算;
4.程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
## GCD 任务
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
执行任务有两种方式:『同步执行』 和 『异步执行』。
两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
1.同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
2.异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
## 队列(Dispatch Queue)
队列是指执行任务的等待队列,即用来存放任务的队列。队列遵循FIFO(先进先出),新任务总是插到队列的末尾,读取任务总是从队列的头部开始读取,没读取一个任务,就会释放一个任务。GCD中,有两种队列:串行队列,并发队列,两者都遵循FIFO原则。
在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
1.串行队列(Serial Dispatch Queue)
每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
2.并发队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。
## GCD 的使用步骤
2.创建一个队列(串行队列或并发队列);
2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
## 队列的创建方法 / 获取方法
可以使用 dispatch_queue_create 方法来创建队列。
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = 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.同步+并发
/**
同步并发执行
在当前线程执行任务,不会开启新线程,执行完一个任务,在执行下一个任务
*/
-(void)syncWithConcurrent{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任务
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
}
2018-05-16 14:38:55.788806+0800 MultiThread[5657:399983] currentThread -- <NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:55.789158+0800 MultiThread[5657:399983] begin
2018-05-16 14:38:56.790163+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:57.790860+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:58.791470+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:59.794281+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:00.795110+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:01.795762+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:01.796005+0800 MultiThread[5657:399983] end
所有的任务都是在当前线程(主线程)中执行的。并没有开启新线程。
按顺序执行任务。并发队列虽然可以开启多个线程,同时执行任务,但是不能创建新线程。所以并发并不存在,实际效果相当于串行。
2.异步 + 并发
/**
可以开启多个线程,交替执行任务
*/
-(void)asyncConcurrent{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任务
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_async(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
2018-05-16 14:45:25.548159+0800 MultiThread[5724:408572] currentThread -- <NSThread: 0x604000068c40>{number = 1, name = main}
2018-05-16 14:45:25.548681+0800 MultiThread[5724:408572] begin
2018-05-16 14:45:25.548907+0800 MultiThread[5724:408572] end
2018-05-16 14:45:26.549549+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}
2018-05-16 14:45:26.549660+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}
2018-05-16 14:45:26.549679+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}
2018-05-16 14:45:27.550524+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}
除了当前线程(主线程),系统有开启了三个线程,任务交替执行。
begin和end先打印了,说明线程并没有等待,开启了新线程,在新线程中执行任务。
5.同步 + 主队列
/**
卡死
*/
-(void)syncMain{
NSLog(@"currentThread -- %@", [NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t queueu = dispatch_get_main_queue();
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"111----%@", [NSThread currentThread]);
}
});
//追加任务
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"222----%@", [NSThread currentThread]);
}
});
dispatch_sync(queueu, ^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"333----%@", [NSThread currentThread]);
}
});
NSLog(@"end");
}
我们将syncMain()方法放到主线程的队列中执行,而syncMain()方法中,又在主线程中追加了打印任务。并发情况下,syncMain()在等值打印任务完成,而打印任务又在等着syncMain()的完成,相互等待,导致卡死。
## GCD 线程间的通信
在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
/**
*线程间通信
*/
-(void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
输出结果:
2019-08-08 14:56:22.973318+0800 YSC-GCD-demo[17573:4253201] 1---<NSThread: 0x600001846080>{number = 3, name = (null)}
2019-08-08 14:56:24.973902+0800 YSC-GCD-demo[17573:4253108] 2---<NSThread: 0x60000181e940>{number = 1, name = main}
可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。
## GCD的其他方法
1.栅栏方法(dispatch_barrier_async)
如果我们需要实现异步请求两个数据,同时B请求必须在A请求完成后才能继续请求,这就需要一种手段分割开A和B请求,这就是栅栏方法
2.延迟执行方法(dispatch_after)
/**
*延时执行方法 dispatch_after
*/
-(void)after {
NSLog(@"currentThread---%@", [NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after---%@", [NSThread currentThread]);
});
}
3.GCD一次性代码(dispatch_once)
-(void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 这里的代码指挥执行一次
});
}
4.循环遍历方法(dispatch_apply)
dispatch_apply方法会开启多个线程来异步遍历数据,无论串行队列,还是并发队列,该方法都会等待全部任务完毕,有点像同步操作。
-(void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
5.GCD队列组(dispatch_group)
有时候会遇到这种情况,要求异步执行两个耗时任务,然后当两个任务完成后调回带主线程,执行主线程的任务。dispatch_group可以把相关的任务归并到一个组内来执行,通过监听组内所有任务的执行情况来做相应处理。
6.GCD信号量(dispatch_semaphore)
dispatch_semaphore是GCD中的信号量,可以处理多线程中线程并发的问题,也可以用作同步处理。
1.创建信号量,里面的参数是表示信号的总量,值必须>=0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2.发送一个信号,信号量的总数会+1
dispatch_semaphore_signal(semaphore);
信号等待,当信号量的总数<=0的时候,会一直等待,直到信号量的总数>0的时候才会继续下面的执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
注意:dispatch_semaphore_wait
当信号量的总数<=0时候,该函数所在的线程就会等待,而信号量的总数>0的时候,该函数就会继续往下执行,同时信号量的总数-1
这里有个等待时间的参数,如果在等待的时间内获得了信号量,那么函数继续往下执行,如果等待时间内信号量一直为0,那么函数也会继续往下执行了
/**
模拟两个窗口售票
*/
-(void)initTickets{
self.semaphoreTicket = dispatch_semaphore_create(1);
self.ticketCount = 10;
dispatch_queue_t queueOne = dispatch_queue_create("com.easyFly.one", 0);
dispatch_queue_t queueTwo = dispatch_queue_create("com.easyFly.Two", 0);
__weak typeof(self) weakSelf = self;
dispatch_async(queueOne, ^{
[weakSelf saleTicket];
});
dispatch_async(queueTwo, ^{
[weakSelf saleTicket];
});
}
-(void)saleTicket{
while (true) {
// dispatch_semaphore_wait(_semaphoreTicket, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) {
self.ticketCount --;
NSLog(@"剩余票 --- %ld 窗口 --- %@", self.ticketCount, [NSThread currentThread]);
}else{
NSLog(@"所有票均已售完");
// 相当于解锁
// dispatch_semaphore_signal(_semaphoreTicket);
break;
}
// dispatch_semaphore_signal(_semaphoreTicket);
}
}
2018-05-17 10:58:17.676200+0800 MultiThread[12006:1085650] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676247+0800 MultiThread[12006:1085651] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085651] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085650] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676578+0800 MultiThread[12006:1085651] 剩余票 --- 7 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676641+0800 MultiThread[12006:1085650] 剩余票 --- 6 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676925+0800 MultiThread[12006:1085650] 剩余票 --- 4 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676751+0800 MultiThread[12006:1085651] 剩余票 --- 5 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.677039+0800 MultiThread[12006:1085650] 剩余票 --- 3 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.677602+0800 MultiThread[12006:1085651] 剩余票 --- 2 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.677828+0800 MultiThread[12006:1085650] 剩余票 --- 1 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.678013+0800 MultiThread[12006:1085651] 剩余票 --- 0 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.678449+0800 MultiThread[12006:1085650] 所有票均已售完
2018-05-17 10:58:17.678646+0800 MultiThread[12006:1085651] 所有票均已售完
解析一下,两个队列相当于两个售票窗口,初始化信号量为1,当一个队列进入售票过程,dispatch_semaphore_wait使得信号量-1,信号量为0,并执行下方售票代码,dispatch_semaphore_signal使得信号量+1,相当于解锁。实现了线程安全。