ios中多线程GCD(Grand Center Dispatch)
- 特别注意ios中主线程又称作为 UI线程, 主要任务就是处理UI事件, 即显示和刷新UI,
- 只有主线程具有直接修改UI的能力, 那些耗时的(从网络获取数据 | 加载图片 | 数据库读取 | IO等)操作要放在子线程(又称为后台线程或者异步线程)中处理, 这样可以提高程序执行效率和资源利用率, 最重要的用户的 UI 的体验也会很好.
- 死锁: 两个或者多个线程都要等待对方完成某个操作才能进行下一步, 从而产生死锁. 如: 在主线程串行队列执行同步任务,会产生死锁.
常见代码示例:
dispatch_queue_t globalQueue = dispatch_get_global_queu(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
//异步 加载数据
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
NSError *error;
NSString *htmlData = [NSString stringWithContentsWithURL:url encoding:NSUTF8StringEncoding error:&error];
//加载完毕切换到 主线程中更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主队列更新UI界面 = %@", htmlData);
});
});
同步和异步
同步和异步决定了要不要开启新的线程
-
同步: 在当前线程中执行任务, 不具备开启新线程的能力.
dispatch_sync(queue, ^{});
-
异步: 在新的线程中执行任务, 具备开启新线程的能力.
dispatch_async(queue, ^{});
并发和串行
并发和串行决定了任务的执行方式
-
并发: 多个任务并发(同时)执行.
并发队列: dispatch_get_global_queue(0, 0);
-
串行: 一个任务执行完毕后, 再执行下一个任务.
主队列: dispatch_get_main_queue();
同步和异步 & 并发和串行
ios中常用的搭配组合:
-
同步--串行(详情看下面自定义串行队列例子), 有特殊情况(主队列要对应异步, 否则会造成死锁)
dispatch_async(dispatch_get_main_queue(), ^{});
-
异步--并行:
dispatch_async(dispatch_get_global_queue(0, 0), ^{});
-
其他情况等同于上面两种情况, 就不多介绍了
GCD中三种队列类型
- 主线程串行队列(main queue)
- 全局并发队列(global queue)
- 自定义队列(custom queue)
- 还有一个就是基于前面的三种队列形成的队列组(group queue)
详细介绍
GCD编程核心:就是dispatch队列.
即: 将任务放到block中, dispatch分发到相对应的队列中去执行.
-
主线程队列(main queue)--> 串行:
即将任务(block)放到主队列中去, 在主线程中执行, 注意主队列默认是串行的(即:若此刻主队列正在执行任务, 那么刚放进行来的block任务就要等待前面的任务block执行完, 才能执行哦).
ios中获取主队列: dispatch_get_main_queue() 主队列中执行同步任务会造成死锁哦: dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"会造成死锁"); });
-
全局并发队列(global queue)--> 并发:
全局并发队列由所有进程共享, 分为(高, 中(默认为中), 低, 后台)四个优先级.
//程序默认的队列级别,一般不要修改: DISPATCH_QUEUE_PRIORITY_DEFAULT == 0 //HIGH DISPATCH_QUEUE_PRIORITY_HIGH //LOW DISPATCH_QUEUE_PRIORITY_LOW //BACKGROUND DISPATCH_QUEUE_PRIORITY_BACKGROUND ios中获取全局并发队列:dispatch_get_global_queue(0, 0) 全局并发队列执行同步任务会导致页面卡顿: dispatch_sync(dispatch_get_global_queue(0, 0), ^{ NSLog(@"会造成页面卡顿"); }); 全局并发队列执行多个任务时, 执行的顺序是不确定的. 因为全局并发队列是由系统默认生成的, 故也无法来控制执行的继续和中断.
-
自定义队列:--> 串行或者并发:
ios中创建队列: dispatch_queue_create() //串行队列创建 dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue_name", DISPATCH_QUEUE_SERIAL); //自定义串行队列 同步执行多个任务 dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"current task");
dispatch_sync(serialQueue, ^{
NSLog(@"最先加入自定义串行队列");
sleep(2);
});
dispatch_sync(serialQueue, ^{
NSLog(@"次加入自定义串行队列");
});
NSLog(@"next task");
//自定义串行队列嵌套在执行同步任务会产生死锁:
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
//该代码段后面的代码都不会执行,程序被锁定在这里
NSLog(@"会执行的代码");
dispatch_sync(serialQueue, ^{
NSLog(@"代码不执行");
});
});
注意: 不要嵌套使用 同步 串行 队列执行任务
//自定义创建并发队列:
dispatch_queue_t conCurrentQueue = dispatch_queue_create("conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//并发队列执行同步是没有意义的(等同于异步), 这里就不多介绍了. 一般情况下, 使用系统默认的全局并发队列就已经足够了,
//推荐使用系统默认的全局并发队列.
```
-
队列组(group queue):
将多个线程进行分组, 最大的好处就是可以获知所有进程的完成情况.
使用场景: 比如说 同时下载多张图片时, 有这么一个需求:要等所有的图片下载完毕后, 才能去更新UI(回到主线程或者去执行其他操作), 这时候就要用到队列组了.
```
ios获取队列组: dispatch_group_create()
通过dispatch_group_notify 可以对队列组中的所有线程进行监听进程完成情况.
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_group_t groupQueue = dispatch_group_create();
NSLog(@"current task");
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务1");
});
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
NSLog(@"并行任务2");
});
dispatch_group_notify(groupQueue, mainQueue, ^{
NSLog(@"groupQueue中的任务 都执行完成,回到主线程更新UI");
});
NSLog(@"next task");
```
GCD中一些系统提供的(常用)dispatch方法
-
延时方法:
dispatch_after(time, queue, ^{}); dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC); dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_after(delayTime, mainQueue, ^{ NSLog(@"3秒后添加到主线程中执行..."); });
-
多次执行某一任务:
dispatch_apply(count, queue, ^(size_t index)); 为了不阻塞主线程, 一般dispatch_apply放在异步并行队列中执行, 执行完了切到主队列中再次执行 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
//第一个参数,3--block执行的次数
//第二个参数,applyQueue--block任务提交到的队列
//第三个参数,block--需要重复执行的任务
dispatch_apply(3, applyQueue, ^(size_t index) {
NSLog(@"current index %@",@(index));
NSThread.sleep(1);
});
NSLog(@"dispatch_apply 执行完成");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"回到主线程更新UI");
});
});
NSLog(@"next task");
//注意: 嵌套使用dispatch_apply会导致死锁。
```
-
只执行一次的代码:
dispatch_once保证在app运行期间, block中的代码只执行一次 ios最常用就是 单例模式 如Person类单例创建方法: static Person *person = nil; + (instanceType)sharedManager { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ person = [self alloc] init]; }); return person; }
-
栅栏函数: 类似队列组的作用.
dispatch_barrier_async(queue, ^{}); 用法: 在并行队列中, 等待在dispatch_barrier_async之前加入队列的任务(并发)全部执行完毕, 再去执行dispatch_barrier_async之后添加进行的任务(注意是并发执行的任务哦). dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 1");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 2");
});
//等之前的任务执行完毕才会执行下面的任务哦.
dispatch_barrier_async(conCurrentQueue, ^{
NSLog(@"dispatch barrier");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 3");
});
dispatch_async(conCurrentQueue, ^{
NSLog(@"dispatch 4");
});
```
网友评论