iOS并发-真相

在开发iOS应用时,无法逃避并发或线程问题。 在许多情况下,都非常需要线程,队列和并发的概念。 这些泛泛的概念确实非常广泛,但是对于苹果公司而言,在开发应用程序时,它本身就可以管理很多线程方面的事情。

首先让我们谈一下并发性—真正的意义是什么,为什么会如此重要。 确切地说, 并发是由CPU处理多个任务 ,仅此而已,没有什么大不容易理解的词:)。 早期的Apple设备安装了1个CPU,可以在多个任务之间进行上下文切换。 2011年晚些时候,iPad和iPhone升级为双核,是的,这是一个很大的举措。 苹果提供了可以用于执行简单任务的GCD(Grand Central Dispatch)和通常用于执行复杂任务的操作队列。 之所以需要并发专业知识的原因之一是保持UI响应能力,这可以通过以下示例来理解:在UITableView中需要滚动图像,而其下载和转换图像则需要时间并且有点慢,因此建议这样做在除主线程之外的其他线程上运行这些任务。

注意:默认情况下,所有UI更新任务都在主线程上执行。 如果我们在主线程上执行繁重或复杂的计算,那么它将导致您的应用程序的UI阻塞并使其无响应。

  • 修改或更新相同资源的任务一定不能同时运行,我们需要确保资源是线程安全的。
  • 术语“任务”和“过程”通常可以互换使用。 不幸的是,术语“多任务”通常用来表示一次管理多个进程的能力,而“多处理”是指具有多个处理器(CPU)的系统。

我们可以创建自己的线程,但是在没有专业知识的情况下使用此选项可能会导致很多问题。 在将任务分配给GCD或操作队列时,系统将自己管理线程。 我们可以创建多个任务并将其分配给队列。 每个任务分为多个单元,每个单元由可用线程执行。 系统将任务分解为多个单元并将其分配给线程。 在下面给出的图中,我们有一个包含6个任务的队列,该队列分为2个线程,“线程1”执行4个任务(紫色,黄色,红色和绿色),“线程2”执行2个任务(蓝色,橙色)。

在GDC和Operation队列之间进行选择取决于我们希望任务之间或与主线程进行通信的方式,因为两者都提供了使同步函数异步运行的各种方法。

一个人在处理并发时可能会面临3个主要潜在问题,而前两个可以通过使用GCD或Operation队列来解决:

  1. 比赛条件
  2. 优先级倒置
  3. 死锁

竞争条件:当两个线程尝试同时访问或更改同一资源时。 这完全取决于线程的调度方式以及它们的启动,睡眠和恢复时间。 Xcode 8具有Thread Sanitizer或TSan来找出潜在的竞争条件代码。 下图显示了通过两个线程访问资源的理想情况,其中线程在不同的时钟周期访问值以进行读/写。

但是有可能在线程2的写操作之前完成了线程2的初始化和读取操作,这在线程2的写语句之后给出了不一致的结果。 这可以通过下面给出的图表来理解。

竞争条件可以通过锁定将使另一个线程等待其完成工作的值来解决,也可以通过将串行任务一次限制在一个特定资源中来使用串行队列来解决。

优先级倒置:

当高优先级工作变得依赖于低优先级工作时,或者成为低优先级工作的结果时,就会发生优先级倒置 。 结果,可能会发生阻塞,旋转和轮询。

在同步工作的情况下,系统将尝试通过在反转期间提高较低优先级工作的QoS(服务质量)来自动解决优先级反转。 在以下情况下会发生这种情况:

  • 当为串行队列上的块调用dispatch_sync()dispatch_wait()
  • 当互斥量由具有较低QoS的线程持有时,调用pthread_mutex_lock()时。 在这种情况下,将持有锁的线程提升到调用方的QoS。 但是,跨多个锁不会发生这种QoS提升。

在异步工作的情况下,系统将尝试解决串行队列上发生的优先级倒置。

让我们考虑一个示例,其中存在3个任务– task1,task2和task3,它们分别具有低,中和高优先级,它们需要一个公共资源。 首先, task1开始执行并锁定资源,而task2启动并暂停task1,但是由于它被task1锁定,它将无法访问公共资源,同时task3启动并暂停task2并开始执行,因为它具有较高的优先级,但是当它需要公用资源,因为它仍被task1锁定,因此它被阻塞。 在这种情况下,低优先级任务和中优先级任务被高优先级任务停止,而需要公共资源的高优先级任务被低优先级任务停止,这是优先级倒置的经典情况,如下所示:

为了解决这个问题,系统以中等优先级开始执行task2,这被称为优先级倒置。 当任务2不再需要共享资源时,任务1在任务3仍在等待的地方开始,然后当任务1使用资源完成时,任务3最终开始使用公共资源。 下面对此进行了很好的解释:

注意:开发人员应首先确保不会发生优先级倒置,因此不会发生系统倒置。 被迫尝试解决。

死锁:

死锁是指至少有两个线程在某个不同的资源上保持锁定,并且都在等待其他资源完成其任务的情况。 而且,没有人能够锁定它所拥有的资源。 (见下图)

在上图中,线程1访问资源1并对其进行阻止,类似地,线程2访问资源2并对其进行阻止。 当线程1也要访问已由线程2使用的资源2时,线程1等待直到它释放,但是当线程2也要访问已被线程1阻塞的资源1时出现问题,这将导致死锁,如两个线程都在等待另一个线程完成。