Swift并行编程:基础知识

大约一年前,我的团队开始了一个新项目。 这次,我们希望利用从先前项目中获得的所有经验。 我们做出的决定之一是使整个模型API异步。 这将使我们能够更改模型的整个实现,而不会影响应用程序的其余部分。 如果我们的应用程序能够处理异步调用,则无论是与后端,缓存还是数据库进行通信都没关系。 它还使我们能够同时工作。

同时,它具有一些含义。 作为开发人员,我们必须了解并发和并行性等主题。 否则,它将使我们陷入困境。 因此,让我们一起学习如何并行编程。

同步与异步

那么,同步处理和异步处理之间的真正区别是什么? 想象一下,我们有一个项目清单。 在同步处理这些项目时,我们从第一个项目开始并先完成它,然后再开始下一个项目。 它的行为与FIFO队列(先进先出)相同。

转换为代码意味着:方法的每个语句将按顺序执行。

代码中的一个示例是简单的回调。 我们可以看到在处理回调之前最后执行的代码。

但让我们想象一下,我们可以同时使用多个工作程序。 他们每个人都将带一个盒子并一直携带。 这会大大提高我们的生产率,不是吗? 由于我们使用多个工人,因此增加的数量与我们拥有的工人数量相同。 只要至少有2名工人同时搬运箱子,他们就会并行进行搬运。

并行是关于同时执行工作。

但是,如果我们只有一名工人并且可能使用更多的工人,会发生什么? 我们应该考虑在处理状态下有多个盒子。 这就是并发性。 可以看作是将A到B的距离分为多个步骤。 工人可以将箱子从A拿到整个距离的中点,然后回到A抓住下一个箱子。 使用多名工人,我们可以使他们所有搬运箱子的距离不同。 这样,我们异步处理盒子。 如果我们有多名工人,我们将并行处理这些箱子。

因此,并行和并发之间的区别很简单。 并行是关于同时进行工作。 并发是关于同时进行工作的选择。 它不必并行,但可以并行。 我们的大多数计算机和移动设备都可以并行工作(由于内核数量的原因),但是您拥有的每个软件肯定可以同时工作。

每个操作系统都提供使用并发的不同工具。 在iOS中,我们拥有默认的工具,例如进程和线程,但是由于其在Objective-C中的悠久历史,因此也有调度队列。

处理

流程是您的应用程序的实例。 它包含执行应用程序所需的所有内容,其中包括堆栈,堆和所有其他资源。

尽管iOS是多任务操作系统,但它不支持一个应用程序执行多个进程。 因此,您只有一个过程。 查看macOS有点不同。 您可以使用Process类产生新的子进程。 它们与父进程无关,但包含父级在创建子进程时拥有的所有信息。 如果您使用的是macOS,下面是创建和执行过程的代码:

苹果公司对上述内容的实施称为“大中央派遣”(简称GCD)。 在iOS中如何处理?

优先继承

优先级反转的解决方案是优先级继承。 在这种情况下,只要线程1被阻塞,就将其优先级交给线程3。 因此,线程3和2具有较高的优先级,并且都可以执行(取决于操作系统)。 3解锁资源后,高优先级又回到线程1,它将继续其原始工作。

原子

原子包含与数据库上下文中的事务相同的想法。 您希望一次将所有值写入一个操作。 当使用int64_t而不具有原子功能时,编译为32位的应用程序可能会出现奇怪的行为。 为什么? 让我们详细了解会发生什么:

  int64_t x = 0线程1: 
x = 0xFFFF
线程2:
x = 0xEEDD

进行非原子操作可能会导致第一个线程开始写入x。 但是,由于我们正在使用32位操作系统,因此必须将写入x的值分成两批0xFF

当Thread2决定同​​时写入x时,可能会按照以下顺序安排操作:

 线程1:part1 
线程2:part1
线程2:part2
线程1:part2

最后,我们将获得:

  x == 0xEEFF 

既不是0xFFFF也不是0xEEDD

使用原子,我们创建一个单一的事务,这将导致以下行为:

 线程1:part1 
线程1:part2
线程2:part1
线程2:part2

结果,x包含值Thread2 set。 Swift本身没有实现原子。 关于Swift进化的建议是添加它,但是目前,您必须自己实现它。

锁是防止多个线程访问资源的简单方法。 线程首先检查它是否可以进入受保护的部分。 如果可以进入,它将锁定受保护的零件并继续。 一旦退出,它将解锁。 如果在输入线程时遇到锁定的部分,它将等待。 通常,通过睡眠并定期唤醒以检查其是否仍处于锁定状态来完成此操作。

在iOS中,可以使用NSLock完成此操作。 但是请注意,解锁时,它必须与锁定的线程处于同一线程。

缺点是它浪费时间,因为它不得不大量分配和更改上下文。 如果您的应用不需要任何高计算能力,则这无关紧要,但是如果遇到任何帧丢失的情况,您可能需要考虑使用其他解决方案(例如Mutex)。

派送壁垒

如果您使用的是GCD,则还有更多选项可以同步代码。 其中之一是调度屏障。 有了这些,我们可以创建需要一起执行的受保护部分的块。 我们还可以控制异步代码执行的顺序。 这样做听起来很奇怪,但请想象您有一个长期的工作要做,可以分为几部分。 这些部分需要按顺序运行,但可以再次分成较小的块。 零件的这些较小块可以异步运行。 现在,可以使用分派屏障来同步较大的部分,而分块可以疯狂运行。

蹦床

蹦床并不是操作系统提供的真正机制。 相反,它是一种模式,可用于确保在正确的线程上调用方法。 这个想法很简单,该方法在开始时检查是否在正确的线程上,否则,在正确的线程上调用自身并返回。 有时您需要使用上述锁定机制来实现等待过程。 每当调用的方法返回一个值时,都是这种情况。 否则,您可以简单地返回。

请勿过于频繁地使用此模式。 这样做并确保您的线程很吸引人,但是同时,这也会使您的同事感到困惑。 他们可能不明白为什么您到处都在更改线程。 在某些时候,它变得杂乱无章,并浪费了时间进行代码推理。

哇,这是一个很沉重的职位。 进行并发编程的选项太多了,这篇文章只是表面介绍。 同时,有很多机制可以做到这一点,需要考虑很多情况。 每当我谈论线程时,我可能都会使每个工作中的人烦恼,但是它们很重要,而且我的同事们开始慢慢同意。 就在今天,我不得不修复一个错误,在该错误中,操作以异步方式访问数组,并且据我们了解,Swift不支持原子操作。 你猜怎么了? 它最终崩溃了。 如果我们所有人都对并发有更多的了解,这可能不会发生,但是说实话,我一开始也没有看到。

了解您的工具是我能给您的最佳建议。 通过以上文章,希望您找到并发的起点,并且是一种控制混乱的方法,这种混乱将在您深入学习后立即显现。 祝好运!

PS:还有更多内容:深入研究Operations和其他高级工具以对抗并发!