GCD的概念
GCD(Grand Central Dispatch),是有Apple公司开发的一个多核编程的解决方案,用以优化应用程序支持多核处理器,是基于线程模式之上执行并发任务。
GCD的优点
1.利用设备多核进行并行运算
2.GCD自动充分使用设备的CPU内核
3.GCD自动管理线程的生命周期(线程创建、线程调度、线程销毁)
4.使用简单
GCD任务和队列
任务:就是执行操作,即可以执行的代码;执行任务有两种方式:同步 和 异步。
- 同步(sync):
阻塞线程: 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行;
不可以开辟新的线程 - 异步(async):
不会阻塞线程:异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
可以开辟新的线程
队列:Dispatch Queue
指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务;
在 GCD 中有两种队列:串行队列 和 并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
-
串行队列(Serial Dispatch Queue): 每次只有一个任务被执行。让任务一个接着一个地执行
-
并发队列(Concurrent Dispatch Queue): 可以让多个任务同时执行。(可以开启多个线程,并且同时执行任务)
GCD 的使用步骤
GCD的使用分为两步:
1. 创建队列
2. 将任务追加到队列中
创建队列
创建队列使用dispatch_queue_create
函数
/*
创建队列
label:队列的唯一标识符
attr:用来识别是串行队列还是并发队列;
DISPATCH_QUEUE_SERIAL 表示串行队列;
DISPATCH_QUEUE_CONCURRENT 表示并发队列;
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr);
*/
dispatch_queue_t queue = dispatch_queue_create("qctt", DISPATCH_QUEUE_SERIAL);
获取队列
串行队列,GCD 默认提供了主队列(Main Dispatch Queue)
//获取主队列
dispatch_queue_t mainqueue = dispatch_get_main_queue();
并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)
/**
参数一表示队列优先级,默认 DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
参数二暂时没用,用 0 即可
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
创建任务
GCD提供了创建同步任务以及异步任务的函数;
同步任务: dispatch_sync
// 同步执行任务创建方法
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%s",__func__);
});
异步任务: dispatch_async
// 异步执行任务创建方法
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%s",__func__);
});
任务和队列不同组合方式的区别
区别 | 串行队列 | 并发队列 | 主队列 |
---|---|---|---|
sync | 没有开启新线程,串行 | 没有开启新线程,串行 | 死锁卡住不执行 |
async | 开启新线程,串行 | 开启新线程,并发 | 没有开启新线程,串行 |
GCD 的组合使用
串行队列 + 同步执行
不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。
/**
* 串行队列 + 同步执行
* 特点:不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。
*/
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:3]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncSerial---end");
运行结果同步顺序执行
串行队列 + 异步执行
会开启新线程。执行完一个任务,再执行下一个任务。
/**
* 串行队列 + 异步执行
* 特点:会开启新线程。执行完一个任务,再执行下一个任务。
*/
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 任务 1
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 任务 2
[NSThread sleepForTimeInterval:3]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncSerial---end");
打印日志:
2022-05-31 10:20:28.125523+0800 DemoTest2022[5271:199261] asyncSerial---end
2022-05-31 10:20:29.125730+0800 DemoTest2022[5271:199349] 1---<NSThread: 0x600003d8fb80>{number = 5, name = (null)}
2022-05-31 10:20:32.131137+0800 DemoTest2022[5271:199349] 2---<NSThread: 0x600003d8fb80>{number = 5, name = (null)}
2022-05-31 10:20:34.132492+0800 DemoTest2022[5271:199349] 3---<NSThread: 0x600003d8fb80>{number = 5, name = (null)}
并发队列 + 同步执行
不会开启新线程,在当前线程执行任务。执行完一个任务,再执行下一个任务。
/**
* 并发队列 + 同步执行
* 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
*/
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 任务 2
[NSThread sleepForTimeInterval:3]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
// 任务 3
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"syncConcurrent---end");
2022-05-31 10:31:42.719133+0800 DemoTest2022[5476:209855] 1---<_NSMainThread: 0x6000032507c0>{number = 1, name = main}
2022-05-31 10:31:45.719679+0800 DemoTest2022[5476:209855] 2---<_NSMainThread: 0x6000032507c0>{number = 1, name = main}
2022-05-31 10:31:46.720250+0800 DemoTest2022[5476:209855] 3---<_NSMainThread: 0x6000032507c0>{number = 1, name = main}
2022-05-31 10:31:46.720599+0800 DemoTest2022[5476:209855] syncConcurrent---end
并发队列 + 异步执行
可以开启多个线程,任务同时执行。
/**
* 并发队列 + 异步执行
* 特点:可以开启多个线程,任务同时执行。
*/
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 任务 2
[NSThread sleepForTimeInterval:3]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 任务 3
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"asyncConcurrent---end");
执行结果:
2022-05-31 10:27:26.350321+0800 DemoTest2022[5398:206005] asyncConcurrent---end
2022-05-31 10:27:27.351422+0800 DemoTest2022[5398:206201] 3---<NSThread: 0x600002d22dc0>{number = 4, name = (null)}
2022-05-31 10:27:28.354696+0800 DemoTest2022[5398:206202] 1---<NSThread: 0x600002d3c7c0>{number = 6, name = (null)}
2022-05-31 10:27:29.352304+0800 DemoTest2022[5398:206200] 2---<NSThread: 0x600002d34e00>{number = 9, name = (null)}
GCD 的其他方法栅栏函数
GCD栅栏方法: dispatch_barrier_async
dispatch_barrier_async
方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async
方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
/**
* 栅栏方法 dispatch_barrier_async
*/
dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:1]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
执行日志:先执行栅栏之前的21,然后执行栅栏,再添加后续任务并执行
2022-05-31 10:48:48.606711+0800 DemoTest2022[5722:224160] 2---<NSThread: 0x600002b12b80>{number = 8, name = (null)}
2022-05-31 10:48:49.607101+0800 DemoTest2022[5722:224161] 1---<NSThread: 0x600002b05d00>{number = 5, name = (null)}
2022-05-31 10:48:51.608045+0800 DemoTest2022[5722:224161] barrier---<NSThread: 0x600002b05d00>{number = 5, name = (null)}
2022-05-31 10:48:52.609360+0800 DemoTest2022[5722:224161] 3---<NSThread: 0x600002b05d00>{number = 5, name = (null)}
2022-05-31 10:48:53.608776+0800 DemoTest2022[5722:224165] 4---<NSThread: 0x600002b0f900>{number = 7, name = (null)}
栅栏函数实现多读单写
#import "UserCenter.h"
@interface UserCenter()
{
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}
@end
// 多读单写模型
@implementation UserCenter
- (id)init
{
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key
{
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key
{
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}
@end
GCD 延时执行方法:dispatch_after
dispatch_after
方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。
/**
* 延时执行方法 dispatch_after
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后异步追加任务代码到主队列,并开始执行
NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
});
}
GCD 执行一次:dispatch_once
在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once
方法。使用 dispatch_once
方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once
也可以保证线程安全。
void
dispatch_once(dispatch_once_t *predicate,
DISPATCH_NOESCAPE dispatch_block_t block);
/**
* 只执行一次 dispatch_once
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行 1 次的代码(这里面默认是线程安全的)
});
GCD 队列组:dispatch_group
场景:当需要异步执行多个耗时任务,然后执行完毕后回到主线程;
dispatch_group_notify
: 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务;dispatch_group_t group = dispatch_group_create(); dispatch_queue_t global = dispatch_get_global_queue(0, 0); dispatch_group_async(group, global, ^{ //添加任务 [NSThread sleepForTimeInterval:2]; NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 dispatch_async(global, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"11111---%@",[NSThread currentThread]); }); }); dispatch_group_async(group, global, ^{ //添加任务 [NSThread sleepForTimeInterval:1]; NSLog(@"2---%@",[NSThread currentThread]); dispatch_async(global, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"222222---%@",[NSThread currentThread]); }); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //等待 1,2 任务执行完成后到主线程执行3 [NSThread sleepForTimeInterval:1]; NSLog(@"3---%@",[NSThread currentThread]); NSLog(@"group end"); });
2022-05-31 14:12:15.717237+0800 DemoTest2022[8494:370194] 2---<NSThread: 0x60000224d940>{number = 9, name = (null)}
2022-05-31 14:12:16.717106+0800 DemoTest2022[8494:370195] 1---<NSThread: 0x600002230980>{number = 4, name = (null)}
2022-05-31 14:12:17.718496+0800 DemoTest2022[8494:369922] 3---<_NSMainThread: 0x600002228ac0>{number = 1, name = main}
2022-05-31 14:12:17.718496+0800 DemoTest2022[8494:370195] 11111---<NSThread: 0x600002230980>{number = 4, name = (null)}
2022-05-31 14:12:17.718496+0800 DemoTest2022[8494:370194] 222222---<NSThread: 0x60000224d940>{number = 9, name = (null)}
2022-05-31 14:12:17.718751+0800 DemoTest2022[8494:369922] group enddispatch_group_wait
: 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_async(group,queue , ^{ // 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 }); dispatch_group_async(group, queue, ^{ // 追加任务 2 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 }); // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"group---end");
2022-05-31 14:36:09.722970+0800 DemoTest2022[8888:390059] 1---<NSThread: 0x6000027da3c0>{number = 3, name = (null)}
2022-05-31 14:36:09.722967+0800 DemoTest2022[8888:390062] 1---<NSThread: 0x6000027de940>{number = 7, name = (null)}
2022-05-31 14:36:09.723212+0800 DemoTest2022[8888:389913] group---enddispatch_group_enter
: 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1;void dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave
: 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1;void dispatch_group_leave(dispatch_group_t group);
当 group 中未执行完毕任务数为0的时候,才会使
dispatch_group_wait
解除阻塞,以及执行追加到dispatch_group_notify
中的任务;/** * 队列组 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 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ // 追加任务 2 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的异步操作都执行完毕后,回到主线程. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"group---end"); }); }
2022-05-31 14:56:53.413320+0800 DemoTest2022[9165:405241] currentThread---<_NSMainThread: 0x600001024500>{number = 1, name = main}
2022-05-31 14:56:53.413467+0800 DemoTest2022[9165:405241] group---begin
2022-05-31 14:56:55.415122+0800 DemoTest2022[9165:405400] 1---<NSThread: 0x60000102e080>{number = 6, name = (null)}
2022-05-31 14:56:55.415122+0800 DemoTest2022[9165:405398] 2---<NSThread: 0x600001078040>{number = 8, name = (null)}
2022-05-31 14:56:57.415797+0800 DemoTest2022[9165:405241] 3---<_NSMainThread: 0x600001024500>{number = 1, name = main}
2022-05-31 14:56:57.416083+0800 DemoTest2022[9165:405241] group---end
GCD 信号量:dispatch_semaphore
信号量是基于计数器的一种多线程同步机制,用来管理对资源的并发访问。
GCD 中的信号量是指持有计数的信号。在 Dispatch Semaphore 中,使用计数来完成这个功能,信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。
Dispatch Semaphore 提供了三个方法:
dispatch_semaphore_create
:创建一个 Semaphore 并初始化信号的总量dispatch_semaphore_signal
:发送一个信号,让信号总量加 1dispatch_semaphore_wait
:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
信号量dispatch_semaphore
主要用于两个方面:保持线程同步、为线程加锁
- 保持线程同步
/** 以AFNetworking源码为例*/
- 为线程加锁
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); for (int i = 0; i < 10; i++) { dispatch_async(queue, ^{ // 相当于加锁 -1操作 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); /* todo: */ // 相当于解锁 +1操作。 计数器>= 0就不阻塞线程 int t = rand()%3;//随机0-3秒休眠耗时操作 [NSThread sleepForTimeInterval:t]; NSLog(@"%d",i); dispatch_semaphore_signal(semaphore); }); }
2022-05-31 17:18:02.966335+0800 DemoTest2022[11107:503565] 0
2022-05-31 17:18:03.970640+0800 DemoTest2022[11107:503570] 1
2022-05-31 17:18:05.972503+0800 DemoTest2022[11107:503564] 2
2022-05-31 17:18:07.976567+0800 DemoTest2022[11107:503566] 3
2022-05-31 17:18:08.980964+0800 DemoTest2022[11107:503569] 4
2022-05-31 17:18:10.984335+0800 DemoTest2022[11107:503568] 5
2022-05-31 17:18:10.984578+0800 DemoTest2022[11107:503572] 6
2022-05-31 17:18:12.985732+0800 DemoTest2022[11107:503573] 7
2022-05-31 17:18:14.991233+0800 DemoTest2022[11107:503574] 8
2022-05-31 17:18:15.995794+0800 DemoTest2022[11107:503575] 9