这篇介绍GCD
什么是GCD
1- 全称Grand Central Dispatch
2- 它是纯C语言开发的一套多线程机制,提供了非常多强大的函数,但它是面向过程的
优点
1- 对于多核运算更有效,会自动利用更多的CPU内核
2- 自动管理线程的生命周期(创建,调度,销毁),你只需要告诉他要执行什么任务即可
特点
GCD有一个队列,队列里存放着任务,GCD统一管理整个队列中的任务,队列分为串行和并行两种
任务 : 你要执行的操作
队列 : 存放任务
队列执行任务方式 : 你若将任务添加至队列后,GCD会自动将队列中的任务取出(先进先出,后进后出),放到对应的线程去执行
1- 同步执行任务 : 在当前线程执行,不开启新线程
2- 异步执行任务 : 在其他线程执行,开启新线程
队列分类
串行队列 : 只有一个线程,加入队列的操作按添加顺序依次完成
并发队列 : 自动开启多个线程,多个任务同时执行,但也是先进来的任务优先处理
特殊队列主队列 : 用来执行主线程上的操作任务
备注
同步和异步决定了是否开启新的线程
串行和并发决定了任务的执行方式
下面做个测试:
分别同步和异步添加三个任务,查看它们在主队列,手动创建串行队列,全局并发队列中开启的线程数,以及执行任务方式.
总结
** 其他**
GCD存在于libdispatch库中,任何iOS程序,在运行时,默认会动态加载这个库,不需要我们手动导入.
举个在并发队列中异步执行的例子
//获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//添加任务到队列中,就可以执行任务
//异步函数:具备开启新线程的能力
dispatch_async(queue, ^
{
NSLog(@"任务1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^
{
NSLog(@"任务2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^
{
NSLog(@"任务3----%@",[NSThread currentThread]);
});
//打印主线程
NSLog(@"主线程----%@",[NSThread mainThread]);
同步函数 + 主队列 = 死锁
原因 :
因为同步任务是,一添加到队列上就要马上执行,而主队列只有一条线程(主线程),此时主线程在等待主队列调度同步任务,而主队列发现主线程上还有任务未执行完,就不会让同步任务添加到主线程上,所以主线程和主队列互相等待.
1- 此时主线程在等待主队列调度同步任务
2- 而主队列在等待主线程执行完已有的任务
NSLog(@"主线程----%@",[NSThread mainThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^
{
NSLog(@"任务1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^
{
NSLog(@"任务2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^
{
NSLog(@"任务3----%@",[NSThread currentThread]);
});
线程之间的通信
dispatch_async(queue, ^
{
//其他操作,例如下载数据等
//子线程回到主线程
dispatch_async(dispatch_get_main_queue(), ^
{
//UI刷新
});
});
示例
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_queue_create("DaQianQian", NULL);
dispatch_async(queue, ^
{
NSLog(@"当前线程%@",[NSThread currentThread]);
//从网络上下载图片
NSURL *urlstr=[NSURL URLWithString:@"http://h.hiphotos.baidu.com/baike/w%3D268/sign=30b3fb747b310a55c424d9f28f444387/1e30e924b899a9018b8d3ab11f950a7b0308f5f9.jpg"];
NSData *data=[NSData dataWithContentsOfURL:urlstr];
UIImage *image=[UIImage imageWithData:data];
NSLog(@"图片加载完毕");
//子线程回到主线程
dispatch_async(dispatch_get_main_queue(), ^
{
self.imageView.image=image;
//打印当前线程
NSLog(@"当前线程%@",[NSThread currentThread]);
});
});
}
线程延迟
调用NSObject方法,不会卡住当前线程
例如 : 延迟2S后,在哪个线程调的就自动回到哪个线程,调用run方法,可以传递参数withObject:nil
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
注意 : 如果队列是主队列,就在主线程执行,如果是并发队列,就会新开子线程执行,但是!同步函数执行,异步函数并不执行!
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_queue_create("QianQian", 0);
dispatch_sync(queue, ^
{
[self performSelector:@selector(test1) withObject:nil afterDelay:5.0];
});
NSLog(@"同步函数执行");
//如果使用的是dispatch_async,则不执行
}
-(void)test1
{
NSLog(@"异步函数中延迟执行----%@",[NSThread currentThread]);
}
原因是:
performSelector withObject afterDelay 方法在子线程中,并不会调用SEL方法,而performSelector withObject 方法会直接调用.原因在于一下两点:
- afterDelay方法是使用当前线程的定时器在一定时间后调用SEL,而无afterDelay的方法是直接调用SEL.
- 子线程中默认是没有定时器的.
解决办法有两种:
解决办法一 : 开启线程的定时器
[[NSRunLoop currentRunLoop] run];
结果如下
dispatch_queue_t queue = dispatch_queue_create("QianQian", 0);
dispatch_async(queue, ^
{
[self performSelector:@selector(test1) withObject:nil afterDelay:5.0];
[[NSRunLoop currentRunLoop] run];
});
NSLog(@"同步异步函数都执行");
解决办法二 : 使用GCD函数dispatch_after来执行定时任务,请往下看:
使用GCD函数,不会卡住当前线程
//参数:什么时间/执行哪个队列/执行什么任务
dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
//例如 : 延迟5S后去主线程执行block中的打印
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^
{
NSLog(@"主队列--延迟执行------%@",[NSThread currentThread]);
});
3- 上一篇提到过的sleepForTimeInterval方法,会卡主线程
[NSThread sleepForTimeInterval: 5];
只执行一次
使用dispatch_once函数, 能保证某段代码, 在整个APP的生命周期, 只被执行1次!
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
// 只执行1次的代码(这里面默认是线程安全的)
});
//例子
@property(nonatomic,assign) BOOL log;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
NSLog(@"该行代码只执行一次");
});
//你也可以用BOOL值判断来控制,但下次进入此控制器,还是会执行
if (_log==NO)
{
NSLog(@"该行代码只执行一次");
_log=YES;
}
}
队列组
假如我们现在有一个需求,要求开两个线程,线程1循环打印1000遍A,线程2循环打印1000遍B,两个线程同时打印,打印完成后,回主线程输出"完成".
按照上面整理的知识,我们会认为,开启一个并发的队列,异步执行操作,然后回主线程即可,代码如下:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
for(int A = 0 ; A < 1000; A++)
{
NSLog(@"A = %d,线程是%@",A,[NSThread currentThread]);
}
for(int B = 0 ; B < 1000; B++)
{
NSLog(@"B = %d,线程是%@",B,[NSThread currentThread]);
}
dispatch_async(dispatch_get_main_queue(), ^
{
NSLog(@"回到主线程---%@",[NSThread currentThread]);
});
});
}
你会发现,A和B是在一个线程中执行的,并且是循环完A才开始循环B,等B循环结束后,再回到主线程.
所以,碰到这种需求的时候,我们应该用队列组来解决
队列组使用场景
1- 你需要异步执行两个耗时的操作
2- 等2个异步操作都执行完毕后,再回到主线程执行操作
队列组使用方法
1- 创建一个组
2- 开启一个任务执行任务1
3- 开启一个任务执行任务2
4- 同时执行任务1任务2操作
5- 等组中的所有任务都执行完毕, 再回到主线程执行其他操作
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
for(int A = 0 ; A < 1000; A++)
{
NSLog(@"A = %d,线程是%@",A,[NSThread currentThread]);
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
for(int B = 0 ; B < 1000; B++)
{
NSLog(@"B = %d,线程是%@",B,[NSThread currentThread]);
}
});
dispatch_group_notify(group,dispatch_get_main_queue(), ^
{
NSLog(@"回到主线程---%@",[NSThread currentThread]);
});
}
这样就能完美实现啦 !