Swift并行编程:可能出什么问题?

在最后几篇文章中,我们研究了控制并发的不同方法。 操作系统提供了一些底层的基础知识。 例如,Apple提供了框架或其他想法(例如Promise),它们在JavaScript中得到了广泛使用。 即使前面已经提到了一些陷阱,但我意识到我没有给予他们足够的荣誉。 因此,为全面起见,本文进行了部分概述。

本文的目的是,如果您不了解并发性,可能会出错。 让我们潜入吧!

原子包含与数据库上下文中的事务具有相同的想法。 您希望一次将所有值写入一个操作。 当使用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设置的值。 Swift本身没有实现原子。 关于Swift进化,有一个建议添加它,但是目前,您必须自己实现它。

就在最近,我不得不修复一个崩溃,该崩溃是由于从两个不同的线程写入一个Array而导致的。 还记得Swift并发中的错误处理吗? 它包含一个错误,很容易忽略。 如果组中的两个操作可以并行运行并且同时失败,会发生什么情况? 他们将尝试同时写入错误数组,这将导致Swift.Array中的“分配容量”崩溃。 要修复它,该数组需要是线程安全的。 一种选择是:同步阵列。
但是通常,您将必须锁定每个写访问权限。
不要误会我的意思,阅读也可能会失败:

一个简单的例子是拥有一个银行帐户,需要执行交易。 此交易分为两个部分:第一笔提款和第二笔存款。

该代码可能类似于以下内容:

关于线程的讨论太多了,还有最后一件事需要提及。 您可能不会遇到这种情况,但是仍然可能发生。 每个线程更改都是上下文更改。 还记得我们作为开发人员经常抱怨切换任务(或被人打扰)使我们效率低下吗? 如果我们进行上下文切换,CPU也会发生同样的情况。 所有预加载的命令都需要刷新,并且短期内无法执行任何命令预测。

那么,如果我们经常切换线程会发生什么呢? CPU将无法再进行任何预测,因此效率低下。 它仅适用于最新命令,并且必须等待下一个命令,这将导致更多开销。
作为一般准则,请尽量不要使用太多线程:

“尽可能多,尽可能少。”

最后需要注意的一点。 即使您正确地完成了所有操作,也可以完全控制同步,锁,内存操作和线程。 Swift编译器不保证保留您的代码顺序。 这可能会导致您的同步机制不符合您编写它们的顺序。

换一种说法:

“快速迁移本身并不是100%线程安全的。”

如果您想确保并发性(例如,使用AudioUnits时),则可能需要返回到Objective-C。

如您所见,并发并不是一个容易的话题。 很多事情都会出错,但是同时,它可以提供极大的帮助。 与往常一样,我们使用的工具仅与开发人员一样好。 如果您提供100%的编写代码,则将无法对其进行调试。 因此,明智地选择工具。

Apple提供了一些用于调试并发的工具,例如活动组和面包屑。 遗憾的是,Swift当前不支持它们(尽管至少对于活动,有一个包装器可以这样做)。

  • Swift并行编程:承诺
  • Swift并行编程:操作
  • 使用Swift进行并行编程的基础