多线程 (四)GCD
学习GCD要掌握几个概念
任务:需要执行的代码块可以看作一个任务
队列:把任务放到队列里,遵循先进先出的原则
队列又分为串行队列和并行队列
串行队列:顺序执行
并发队列:同时执行多个任务
同步:在当前线程执行 (不开辟新线程)
异步:在另一条线程执行(会开辟新线程)
gcd是支持arc的,不用我们进行内存管理
1.串行队列,同步添加一个任务(这个操作其实没有什么意义,这里仅仅作一个示例)
- (void)serialQueueSync { //1.创建串行队列,注意:第一个参数为队列标识C语言前面不用带@ dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL); //2.同步执行任务 //一般只要向串行队列添加同步任务,会马上执行 dispatch_sync(queue, ^{ NSLog(@"这是一个同步任务,在线程:%@中执行",[NSThread currentThread]); }); }
2.串行队列,异步添加一个任务
- (void)serialQueueAsync { //1.创建串行队列,注意:第一个参数为队列标识C语言前面不用带@ dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL); //2.异步执行任务 dispatch_async(queue, ^{ NSLog(@"这是一个异步任务,在线程:%@中执行",[NSThread currentThread]); }); }
下面我们用一个for循环来理解串行队列和并发队列的区别
串行队列
- (void)serial
{
dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
//异步执行任务
for(int i = 0; i < 5; i++)
{
dispatch_async(queue, ^{
NSLog(@"这是一个异步任务,i = %d ,在线程:%@中执行",i,[NSThread currentThread]);
});
}
}
结果可以看到只开辟了一个线程,所有任务排队在这个线程里执行
并发队列
- (void)concurrent
{
//并发队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
//异步执行任务
for(int i = 0; i < 5; i++)
{
dispatch_async(queue, ^{
NSLog(@"这是一个异步任务,i = %d,在线程:%@中执行",i,[NSThread currentThread]);
});
}
}
结果可以看到开辟了多条线程同时执行任务
并发队列同步执行:不会开辟新线程
- (void)concurrentSync { //并发队列 dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); //同步执行任务 for(int i = 0; i < 5; i++) { dispatch_sync(queue, ^{ NSLog(@"这是一个异步任务,i = %d ,在线程:%@中执行",i,[NSThread currentThread]); }); } }
总结:同步异步决定了要不要开启新线程,串行和并发决定任务的执行方式
- 同步:不具备开辟线程的能力
- 异步:能开辟新线程
- 并发:多个任务并发(同时)执行
- 串行:按顺序执行任务
串行队列同步执行:不开新线程,在原来线程里面顺序执行
串行队列异步执行:开一条新线程,在新线程里顺序执行
并发队列异步执行:开多条线程(我们并不能控制开启多少条线程,由GCD底层帮我们完成),并发执行任务
并发队列同步执行:不开线程,在原来线程里顺序执行
可以看出,串行队列同步执行和并发队列同步执行对多线程来说没有什么实际意义,开发中也基本不会这么去用
主队列
1.当程序启动时,就会创建一个主线程,同时有一个主队列(iOS开发中默认UI更新全在主线程中完成)
2.主队列负责在主线程上调度任务
3.异步添加任务到主队列不会开启新线程,任务在主线程中执行
4.异步添加到主队列的任务并不一定马上执行,而是顺序等待任务执行
5.同步添加任务到主队列,这是一种十分愚蠢的做法,永远不要这么去做,下面会做说明
下面的代码能帮助我们理解
异步添加任务到主队
- (void)mainQueue { //获取主队列 dispatch_queue_t mainQueue = dispatch_get_main_queue(); for (int i = 0; i < 5; i++) { NSLog(@"添加第%d个任务",i);
//异步添加 dispatch_async(mainQueue, ^{ NSLog(@"i = %d thread = %@",i,[NSThread currentThread]); }); } }
打印结果可以看出任务全部添加了之后才顺序执行
同步添加任务到主队列 (不要这么做,这里仅做示例)
- (void)mainQueue { //获取主队列 dispatch_queue_t mainQueue = dispatch_get_main_queue(); for (int i = 0; i < 5; i++) { NSLog(@"添加第%d个任务",i); //同步添加 dispatch_sync(mainQueue, ^{ NSLog(@"i = %d thread = %@",i,[NSThread currentThread]); }); } }
从打印结果我们能发现主线程被阻塞了(在view上面添加一些UI控件能更清楚的发现这一点),出现这种情况的原因是:同步任务需要马上执行,但是主线程正在执行添加任务,此时添加任务又在等待同步任务执行完成,就造成了互相等待的局面,阻塞了主线程,造成死锁(死锁的概念可以看我的另一篇博文iOS中的锁)
全局队列
全局队列和并发队列的区别(这两种队列用法相似)
1.全局队列没有标示,并发队列有
2.全局队列供所有应用程序使用
3.MRC中,并发队列需要我们进行内存管理,全局队列不需要
//获得全局队列,第一个参数是指队列的服务质量(优先级)一般使用默认0,第二个参数是保留参数也填0 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
线程间的通信(当我们在其他线程中做完数据处理,想把这些数据显示到界面的时候,就要进行线程间通信,iOS默认更新UI的操作都在主线程中执行)
- (void)communicate { dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(globalQueue, ^{ //在这里进行异步操作 dispatch_async(mainQueue, ^{ //回到主线程更新UI }); }); }
延迟执行(下例本质是延迟5s把任务添加到队列)
几个参数的含义
dispatch_time(何时, 经过多少纳秒)
NSEC_PER_SEC :1000000000ull
所以我们要使用3 * NSEC_PER_SEC:这样才算3秒
- (void)delay { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"这个任务延迟5s执行"); }); }
调度组 (应用场景:多个任务全部完成了,再统一通知用户,通过轮询机制判断所有组里的任务是否完成)
- (void)group { //创建一个调度组 dispatch_group_t group = dispatch_group_create(); dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); dispatch_queue_t mainQueue = dispatch_get_main_queue(); //创建多个任务 dispatch_group_async(group, globalQueue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"任务1完成"); }); dispatch_group_async(group, globalQueue, ^{ [NSThread sleepForTimeInterval:3]; NSLog(@"任务2完成"); }); dispatch_group_async(group, globalQueue, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"任务3完成"); }); //获得组里面所有任务的完成通知 dispatch_group_notify(group, mainQueue, ^{ NSLog(@"所有任务完成"); }); }
dispatch_once(只执行一次,受线程保护,常用来设计单例)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self once]; [self once]; } - (void)once { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"只会执行一次"); }); NSLog(@"可以执行多次"); }