GCD中的两个核心概念
- 任务
任务指的是放入GCD中的操作,一般以Block的方式进行,执行任务的操作有两种
- 同步执行:不会开启新的线程,在当前线程中执行,表现为同步函数sync
- 异步执行:拥有开启新线程的执行任务的能力,表现为异步函数async
- 队列
任务队列是用来存放任务的队列,采用先进先出的原则,队列也分为两种
- 串行队列:队列中的任务一个接一个的执行,不会开启新的线程
- 并发队列:在异步函数中会开启多条线程,同时执行任务
创建队列
- 创建串行队列
// 创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
- 主队列也为一个特殊的串行队列,不需要创建,可以直接获取
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
- 创建并发队列
// 创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 全局并发队列也不需要创建,可以直接获取
// 获取全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
创建任务
- 创建同步执行的任务
// 同步函数+串行队列
dispatch_sync(serialQueue, ^{
// 不会开启新的线程
NSLog(@"%@",[NSThread currentThread]);
});
// 同步函数+主队列
dispatch_sync(mainQueue, ^{
// 不会开启新的线程
NSLog(@"%@",[NSThread currentThread]);
});
// 同步函数+并发队列
dispatch_sync(concurrentQueue, ^{
// 不会开启新的线程
NSLog(@"%@",[NSThread currentThread]);
});
- 创建异步执行的任务
// 异步函数+并发队列
dispatch_async(concurrentQueue, ^{
// 开启新的线程
NSLog(@"%@",[NSThread currentThread]);
});
// 异步函数+串行队列
dispatch_async(serialQueue, ^{
// 开启一条后台线程,串行执行任务
NSLog(@"%@",[NSThread currentThread]);
});
// 异步函数+主队列
dispatch_async(mainQueue, ^{
// 不开起新的线程
NSLog(@"%@",[NSThread currentThread]);
});
代码验证
- 串行队列+同步函数
#pragma mark - 串行队列+同步函数
- (void)configSyncSerialQueue {
NSLog(@"开始执行");
NSArray *titleArray = @[@"第一个任务",@"第二个任务",@"第三个任务"];
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue""", DISPATCH_QUEUE_SERIAL);
for (NSString *str in titleArray) {
dispatch_sync(serialQueue, ^{
NSLog(@"串行队列+同步函数:%@--%@",str,[NSThread currentThread]);
});
}
for (NSString *str in titleArray) {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"主队列+同步函数:%@--%@",str,[NSThread currentThread]);
});
}
}
没有开启新的线程,均在主线程中执行
- 串行队列+异步函数
#pragma mark - 串行队列+异步函数
- (void)configAsyncSerialQueue {
NSLog(@"开始执行");
NSArray *titleArray = @[@"第一个任务",@"第二个任务",@"第三个任务"];
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue""", DISPATCH_QUEUE_SERIAL);
for (NSString *str in titleArray) {
dispatch_async(serialQueue, ^{
NSLog(@"串行队列+异步函数:%@--%@",str,[NSThread currentThread]);
});
}
for (NSString *str in titleArray) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主队列+异步函数:%@--%@",str,[NSThread currentThread]);
});
}
}
可以发现,串行队列+异步函数的组合开启了新的线程,而主队列+异步函数确没有开启新的线程,任在主线程执行任务.
#pragma mark - 并发队列+同步函数
- (void)configSyncConcurrentQueue {
NSLog(@"开始执行");
NSArray *titleArray = @[@"第一个任务",@"第二个任务",@"第三个任务"];
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue""", DISPATCH_QUEUE_CONCURRENT);
for (NSString *str in titleArray) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"并发队列+同步函数:%@--%@",str,[NSThread currentThread]);
});
}
for (NSString *str in titleArray) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"全局并发队列+同步函数:%@--%@",str,[NSThread currentThread]);
});
}
}
可见,同步函数不具有开启新线程的能力
#pragma mark - 并发队列+异步函数
- (void)configAsyncConcurrentQueue {
NSLog(@"开始执行");
NSArray *titleArray = @[@"第一个任务",@"第二个任务",@"第三个任务"];
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue""", DISPATCH_QUEUE_CONCURRENT);
for (NSString *str in titleArray) {
dispatch_async(concurrentQueue, ^{
NSLog(@"并发队列+异步函数:%@--%@",str,[NSThread currentThread]);
});
}
for (NSString *str in titleArray) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"全局并发队列+异步函数:%@--%@",str,[NSThread currentThread]);
});
}
}
开启了新的线程,任务并发执行
注意:在使用过程中需要注意死锁问题!
所谓死锁,通常指有两个线程A和B都卡住了,A在等B ,B在等A,相互等待对方完成某些操作。A不能完成是因为它在等待B完成。但B也不能完成,因为它在等待A完成。于是大家都完不成,就导致了死锁
#pragma mark - 死锁案例
- (void)deadLock {
NSLog(@"任务1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2");
});
NSLog(@"任务3");
}
- 任务1,2,3都加入到了主队列中
- 同步函数+主队列不会开启新的线程
- 所有任务都在主线程执行
首先执行任务1,接下来,程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3.但这是主队列,是一个特殊的串行队列,有任务来,会将任务加到队尾,然后遵循FIFO原则执行任务.那么,现在任务2就会被加到最后,任务3排在了任务2前面.但是任务3要等任务2执行完才能执行,任务2又排在任务3后面,意味着任务2要在任务3执行完才能执行,所以他们进入了互相等待的局面,程序就卡在了这里,这就是死锁.
GCD的具体应用
- 线程间通信
在iOS开发中,主线程有称为UI线程,用来处理UI事件.其他耗时操作通常放在子线程中进行,比如网络请求等.通过网络请求回来的数据通常需要用UI展示,这是就需要从子线程回到主线程,进而就产生了线程间通信.
#pragma mark - 线程间通信
- (void)GCDCommunication {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 全局并发队列中异步请求数据
NSArray *dataArray = @[@"我是第1条数据",@"我是第2条数据",@"我是第3条数据"];
for (NSString *dataStr in dataArray) {
NSLog(@"%@---我当前的线程是:%@",dataStr,[NSThread currentThread]);
}
// 请求数据完成,回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"我当前的线程是:%@",[NSThread currentThread]);
});
});
}
可以看到,在子线程中请求完数据之后又回到了主线程中执行任务
- GCD定时器
首先NSTimer可以做定时器,但是它受到RunLoop的Mode影响,不是特别准确,而且容易造成循环引用的问题.GCD定时器可以规避这些问题.
int count = 0;
- (void)GCDTimer {
self.gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); // 从现在开始两秒后执行
dispatch_source_set_timer(self.gcdTimer, startTime, (int64_t)(2.0 * NSEC_PER_SEC), 0); // 每两秒执行一次
// 定时器回调
dispatch_source_set_event_handler(self.gcdTimer, ^{
NSLog(@"CGD定时器-----%@",[NSThread currentThread]);
count++;
if (count == 5) { // 执行5次,让定时器取消
dispatch_cancel(self.gcdTimer);
self.gcdTimer = nil;
}
});
// 启动定时器: GCD定时器默认是暂停的
dispatch_resume(self.gcdTimer);
}
- GCD控制并发
GCD中控制并发可以通过
dispatch_group 队列组
dispatch_barrier 栅栏函数
dispatch_semahpore 信号量
来控制
1.dispatch_group
在某些特殊的场景下我们需要同时执行多个耗时任务,并且在多个任务都完成的之后在回到主线程刷新UI,此时就可以使用dispath_group了
#pragma mark - configDispatch_group
- (void)configDispatch_group {
dispatch_group_t gcdGroup = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(gcdGroup, queue, ^{
NSLog(@"执行第一个任务");
});
dispatch_group_async(gcdGroup, queue, ^{
NSLog(@"执行第二个任务");
});
dispatch_group_async(gcdGroup, queue, ^{
NSLog(@"执行第三个任务");
});
dispatch_group_notify(gcdGroup, dispatch_get_main_queue(), ^{
NSLog(@"回到了主线程");
});
}
在执行完了任务1,2,3之后回到了主线程,起到了控制的作用
- dispatch_barrier 栅栏函数
当一个任务的执行与否依赖于上一个任务执行的结果的时候我们可以使用dispatch_barrier,栅栏函数的作用在于控制并发任务执行的先后顺序
#pragma mark - dispatch_barrier
- (void)configDispatch_barrier {
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行第一个任务--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行第二个任务--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行第三个任务--%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"我是栅栏,前边的任务都执行完了,在执行下边的任务--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行第四个任务--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行第五个任务--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"执行第六个任务--%@",[NSThread currentThread]);
});
}
注意:这里的dispatch_queue_t queue中的queue必须为dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)创建的,使用全局并发队列不起作用
- dispatch_semahpore 信号量
有时候在开发中我们希望执行完一个任务(成功/失败)才接着执行下一个任务,这是我们可以使用信号量来控制
信号量运行准则:
信号量就是一个资源计数器,当其不为0时线程正常运行,为0时则阻塞当前线程.
实现原理:
使用信号量的PV操作来实现互斥.P:信号量-1,V:信号量+1
例如:
默认初始信号量为1
当A正常运行,使用资源;对信号量实施P操作,信号量-1
当B期望使用资源来正常运行,发现信号量为0(阻塞),B一直等待
A执行完成,进行V操作,释放资源,信号量+1
B检测到信号量不为0,则正常运行
#pragma mark - 信号量
- (void)configDispatch_semaphore {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSArray *titleArray = @[@(1),@(1),@(1),@(0),@(1)];
for (int i = 0; i<titleArray.count; i++) {
int number = [titleArray[i] intValue];
dispatch_async(queue, ^{
//信号量为0是则阻塞当前线程,不为0则减1继续执行当前线程
dispatch_wait(semaphore, DISPATCH_TIME_FOREVER);
if (number) {
NSLog(@"%d--当前线程:%@",i,[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
} else {
NSLog(@"%d--当前线程:%@",i,[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
}
});
}
}
可见,信号量起到了控制任务执行顺序的作用
- GCD延时函数
#pragma mark - GCD延时函数
- (void)configDispatch_after {
NSLog(@"开始执行");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"两秒后我执行了");
});
}
- GCD一次性代码
通常用于创建单列
#pragma mark - GCD一次性代码
- (void)configDispatch_once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 这里写只需要执行一次的代码,默认线程安全
});
}
- GCD快速迭代(遍历)
GCD快速遍历与for,while循环不同,它会开启新的线程来遍历(在并发队列中)
- (void)configDispatch_apply {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(15, queue, ^(size_t index) {
NSLog(@"执行第%ld个任务,当前线程为%@",index,[NSThread currentThread]);
});
}
可以看出,GCD快速迭代是在不同线程中执行的,而且还包含了主线程.
注意,这里的快速迭代是在并发队列中执行的,如果放在串行队列中,操作会在主线程中执行,没有起到快速迭代的作用
- (void)configDispatch_apply {
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(15, queue, ^(size_t index) {
NSLog(@"执行第%ld个任务,当前线程为%@",index,[NSThread currentThread]);
});
}
同时应该注意,这里不要使用主队列,在快速迭代使用的线程中包括了主线程,会造成死锁现象
以上内容就是我在iOS开发过程中所理解和使用到的GCD相关内容,如有严谨的地方请读者纠正,谢谢!
以上代码demo地址:https://github.com/geekGetup/GCD