在Swift中探索内存安全和独占访问

在Swift中,我们有一个非常有趣的概念,它称为SE-176上提出并在Swift 4中实现的“ 对内存的独占访问”。但这甚至意味着什么? 好吧,互斥访问是一个规则,强制所有对变量的潜在修改(写)必须与对该变量的任何其他访问互斥。 例如,在多线程环境中工作时,我们有多个线程访问一个变量,并且一个或多个线程可以修改该值,而该变量已被另一个变量修改,则无法从任何读取器或写入器访问该变量。作家。 您可以在提案文档中阅读有关此内容的更多信息。

如何保证独家访问?

通常,如果您知道一个变量将被多个线程修改并读取,则可能会在此调用周围使用NSLock或NSRecursiveLock,以确保任何时候在该代码区域中只有一个线程处于活动状态。

因此,问题是…如果我们锁定了对变量的更改(写),那么数据竞争将如何发生,实际上,在这种情况下,甚至没有任何写操作发生? 🤔

那是我所谈论的有趣的行为。

对于值类型的变异函数,发生的事情是将self作为inout参数隐式传递给该函数。 在内存访问的上下文中, inout参数具有写访问权限,该访问权限自动从函数的开头开始,并在函数完成时结束。 因此,在该示例中,即使我们不对函数内部的数组进行突变,整个方法的执行也具有长期的写访问权限。

从Apple文档中…

函数可以对其所有输入输出参数进行长期写访问。 在评估了所有非输入参数之后,才开始对输入输出参数进行写访问,并持续该函数调用的整个过程。 如果有多个输入/输出参数,则写入访问的开始顺序与参数出现的顺序相同。

您可以在“ 对In-Out参数的访问冲突”部分的“ Memory Safety [1]”文档中找到此内容。

因此,当我们在第一个队列上调用node.clearChilds()self进行写访问时(也就是节点),然后在第二个队列上再次尝试对node.clearChilds()进行访问时,就会发生数据争用。节点,并尝试在self.childs.isEmpty调用中读取self

解决方案非常简单。 如果我们知道对变量函数的写访问已经开始,那么我们将需要锁定此方法的调用方。 因此,我们可以确保对该内容的专有访问权:))

让我们修复代码,看看会发生什么

似乎一切正常,没有更多的数据争夺了:))

上面示例中解决此问题的另一种方法可能是将Node类型更改为而不是结构,因此您没有这种行为,因为在快速类中没有变异方法,而self只是隐式传递给该功能,因此不会启动长期写访问。

当我们在多线程环境中工作时,我们大多数Swift开发人员basically的日常生活基本上是什么,这些概念和细节对于了解非常有用,并且可以使我们避免很多怪异的bug并节省很多调试时间时间。
在下面的参考部分中,有很多关于内存安全,锁,线程安全以及我们在这里探讨的所有其他主题的精彩内容。

这就是本文的全部,希望您喜欢🙂

如果我有问题或您有任何意见或疑问,请告诉我。 我很高兴收到您的反馈feedback

您可以在Twitter上@ LucianoPassos11找到我。

感谢您阅读🙂

  1. Swift编程语言(Swift 4.1):内存安全。 https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/MemorySafety.html
  2. [swift-evolution] [评论] SE-0176:强制对内存进行独占访问。 https://github.com/apple/swift-evolution/blob/master/proposals/0176-enforce-exclusive-access-to-memory.md
  3. 2017年10月27日,星期五问答:锁,线程安全和Swift:2017年版。 https://www.mikeash.com/pyblog/friday-qa-2017-10-27-locks-thread-safety-and-swift-2017-edition.html
  4. 主人处的swift / OwnershipManifesto.md·apple / swift https://github.com/apple/swift/blob/master/docs/OwnershipManifesto.md