云计算百科
云计算领域专业知识百科平台

iOS: GCD入门

文章目录

  • GCD
    • 理解任务, 队列, 线程
    • 为什么需要GCD
    • 队列
      • 串行队列
      • 并发队列
    • sync 和 async
      • 什么是 async
      • 什么是 sync
      • **并发队列和串行队列与sync 和 async 的关系**
    • 主队列, 主线程与死锁
      • 什么是主队列
      • 主队列与主线程
      • 死锁(Deadlock)
    • 全局队列

GCD

GCD 全称:Grand Central Dispatch,

它是 Apple 提供的一套并发编程 API。最简单地说:GCD 负责把任务提交给队列,然后由系统负责调度这些任务执行。

理解任务, 队列, 线程

先看下面一段代码:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"Hello GCD");
});

在这段代码中, 实际上有三个东西:

任务 -> 队列 -> GCD调度执行

代码中的

^{
NSLog(@"Hello GCD");
}

是任务, 而

dispatch_async(...)

是队列, 最后

dispatch_async(...)

就是 把任务提交给队列

注意: 队列不是线程

任务就是 我要干什么

例如:

^{
NSLog(@"下载图片");
}

这是一个任务

^{
int a = 10;
int b = 20;
NSLog(@"%d", a + b);
}

这也是一个任务

队列可以理解为任务的管理者, 例如:

队列

├── 任务 A
├── 任务 B
├── 任务 C
└── 任务 D

GCD 会根据队列的规则决定这些任务应该按照什么方式被调度

线程才是真正执行代码的执行实体

为什么需要GCD

假如需要加载一张很大的照片

UIImage *image = [self loadBigImage];

这个操作非常耗时

如果它发生在主线程, 就会发生: 主线程 -> 加载图片 -> 等待 -> 等待 -> 等待 -> UI卡住

用户就会看到APP卡顿, 界面无法响应, 我们希望: 主线程负责UI, 后台线程负责耗时任务

GCD就可以帮我们完成任务调度, 例如:

dispatch_async(dispatch_get_global_queue(0, 0), ^{

UIImage *image = [self loadBigImage];

dispatch_async(dispatch_get_main_queue(), ^{

self.imageView.image = image;

});
});

这里就出现了非常经典的 iOS 开发模式:

后台执行耗时操作 -> 得到结果 -> 回到主线程更新 UI

队列

队列有两个核心属性:

  • 串行 Serial: 一次执行一个任务
  • 并发 Concurrent: 允许多个任务人同时执行

串行队列

dispatch_queue_t queue = dispatch_queue_create("com.demo.serial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务 A");
});
dispatch_async(queue, ^{
NSLog(@"任务 B");
});
dispatch_async(queue, ^{
NSLog(@"任务 C");
});
}

这个队列是串行(Serial) 队列, 所以一定满足: 同一时刻最多执行一个任务

并发队列

dispatch_queue_t queue = dispatch_queue_create("com.demo.current", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"任务 A");
});
dispatch_async(queue, ^{
NSLog(@"任务 B");
});
dispatch_async(queue, ^{
NSLog(@"任务 C");
});
}

并发队列中多个任务可以同时执行

这里有一个需要注意的地方就是: 这里并发队列不一定创建了三个线程

队列只负责描述任务应该如何被调度, 真正使用多少线程, 由系统的线程池和调度机制决定

sync 和 async

dispatch_sync 和 dispatch_async 也就是 同步提交和异步提交, 这里的 sync 和 async 首先描述的是 提交任务的那个线程, 要不要等待任务执行结束, 而不是 sync = 主线程, async = 子线程

什么是 async

看下面代码:

dispatch_async(queue, ^{
NSLog(@"task");
});

这里可以理解为: 把任务提交给queue, 然后当前线程不用等待这个任务执行完成, 继续向下执行.

dispatch_queue_t queue = dispatch_queue_create("com.demo.current", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
});
NSLog(@"3");

这里的执行结果是

1
3
2

这是因为:

NSLog(@"1");

提交任务 2

不等待

NSLog(@"3")

与此同时:

任务 2

等待 queue 调度

执行

什么是 sync

dispatch_sync(queue, ^{
NSLog(@"2");
});

这里意思是: 把任务提交给 queue, 然后当前线程必须等待这个任务执行结束, 才能继续往下走.

所以:

NSLog(@"1");

dispatch_sync(queue, ^{
NSLog(@"2");
});

NSLog(@"3");

执行顺序一定是:

1

2

3

因为 sync 会等待。

这是 GCD 学习中非常重要的第一条公式:

sync
=
提交任务
+
等待任务执行完成
+
当前线程继续

而:

async
=
提交任务
+
不等待
+
当前线程继续

并发队列和串行队列与sync 和 async 的关系

sync/async 决定“是否阻塞当前线程”和“是否可能开新线程” 串行/并发队列 决定“任务如何被取出执行”(一个接一个 vs 同时多个)

同步异步函数 同步函数(sync)和异步函数(async),只能决定能否开启新的线程执行任务,不能决定函数是串行执行还是并行执行

同步:

  • 在当前线程中执行,不具备开启新线程的能力
  • 队列后面的内容需要等到同步函数返回后才可以继续执行

异步:

  • 拥有开创新线程的能力, 但是不一定会开启新线程
  • 后面的内容不需要等异步函数返回后执行,后面的内容可以直接执行,所以需要开启新线程,因此具备开启新线程的能力

同步函数: 同步函数会阻塞当前函数的返回, 异步函数会立即执行,执行下面的代码

同步函数后面的内容要等到同步函数返回才可以执行,异步函数后面的内容不需要等异步函数返回才执行,可以直接执行,异步函数里面的内可能需要开启一个新线程来执行

串行并行队列 主要影响任务的执行方式.能不能开启新线程取决于上面的函数

并发:多个任务并发执行

串行:一个任务执行完毕后才去执行下一个任务

函数队列类型是否开新线程执行方式是否阻塞当前线程
async 并发队列 ✅ 多个 并发
async 串行队列 ✅ 1个 串行
async 主队列 串行
sync 并发队列 串行
sync 串行队列 串行
sync 主队列 💀 死锁

“async/sync 的优先级在队列之上”

准确地说应该是:

sync/async 决定"执行机制"(是否阻塞、是否开线程) 队列类型决定"调度策略"(串行还是并发) 但 sync/async 的约束会覆盖队列的特性

主队列, 主线程与死锁

什么是主队列

dispatch_get_queue(), 它返回的就是:

主队列: dispatch_get_main_queue()

主队列是一个特殊的串行队列

主队列与主线程

主队列与主线程是两个不同的东西

主队列

负责管理提交给它的任务

主线程

真正执行代码

但是主队列和主线程之间存在非常紧密的关系:

Main Queue

任务

Main Thread

执行

所以在正常情况下, 提交到主线程的任务, 会在主线程执行

死锁(Deadlock)

什么是死锁

死锁发生在“同步(Sync)” + “当前串行队列”。 经典的错误代码如下,运行必崩溃:

// 这段代码跑在主线程(主队列是串行的)
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"我执行了吗?"); // 永远不会打印!APP卡死!
});

主队列是串行的,里面正放着当前这个 viewDidLoad 任务。现在往主队列里同步追加一个Block,同步意味着“我必须等你执行完,我才能往下走”。但主队列在等 viewDidLoad 执行完才能处理Block,而 viewDidLoad 在等Block执行完才结束。互相等待 -> 死锁。

全局队列

dispatch_get_global_queue()

什么是全局队列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

它是Apple提供的系统队列

特点: 并发队列

它类似: Concurrent Queue, 可以同时执行多个任务

赞(0)
未经允许不得转载:网硕互联帮助中心 » iOS: GCD入门
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!