我看过an article about immutability/struct in Swift。有一部分说:
在函数式编程中,副作用通常被认为是不好的, 因为它们可能以意想不到的方式影响您的代码对于 例如,如果在多个位置引用对象,则每次更改 自动发生在每个地方。正如我们在中看到的那样 介绍,在处理多线程代码时,这很容易 导致错误:因为您正在检查的对象可以修改 从不同的主题,您的所有假设都可能无效。
使用Swift结构,变异没有同样的问题。该 结构的突变是局部的副作用,只适用于 当前结构变量。因为每个结构变量都是唯一的(或者 换句话说:每个结构值只有一个所有者),它几乎就是 不可能以这种方式引入错误。 除非您指的是 跨线程的全局结构变量,即。
在银行帐户示例中,帐户对象有时必须变异或更换。使用struct
不足以保护帐户免受从多线程更新的问题。 如果我们使用某个锁或队列,我们也可以使用class
实例而不是struct
,对吗?
我尝试编写一些我不确定会导致竞争条件的代码。主要思想是:两个线程首先获取一些信息(Account
的实例),然后根据获取的内容覆盖保存信息的变量。
struct Account {
var balance = 0
mutating func increase() {
balance += 1
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// method1()
method2()
}
func method1() {
var a = Account(balance: 3) {
didSet {
print("Account changed")
}
}
// https://stackoverflow.com/questions/45912528/is-dispatchqueue-globalqos-userinteractive-async-same-as-dispatchqueue-main
let queue = DispatchQueue.global()
queue.async {
a.increase()
print("global: \(a.balance)")
}
DispatchQueue.main.async {
a.increase()
print("main: \(a.balance)")
}
}
func method2() {
var a = Account(balance: 3) {
didSet {
print("Account changed")
}
}
func updateAccount(account : Account) {
a = account
}
let queue = DispatchQueue.global()
queue.async { [a] in
var temp = a
temp.increase()
updateAccount(account: temp) // write back
print("global: \(temp.balance)")
}
DispatchQueue.main.async { [a] in
var temp = a
temp.increase()
updateAccount(account: temp)
print("main: \(temp.balance)")
}
}
}
如果我们不修改变量a
(或者只使用let
使其成为常量),使用struct
取代{的好处是什么? {1}},就线程安全而言,对于这样一个简单的结构? There may be other benefits。
在现实世界中,class
应该在队列中修改,或者受到某些锁定的保护,但是“不变性对线程安全有帮助”的想法来自哪里?
在上面的例子中,两个线程是否仍然可以获得相同的值,从而产生错误的结果?
来自an answer on StackExchange的引用:
不变性让你有一件事:你可以读取不可变对象 自由地,不用担心它在你下面的状态变化
这样做的好处是只读吗?
Java中存在类似的问题,例如: Does immutability guarantee thread safety?和Immutable objects are thread safe, but why?。
我还在Java中找到了关于这个主题的a good article。
答案 0 :(得分:2)
我不确定是否100%理解您要提出的问题,但是由于最近我一直在研究Swift结构,因此我还是会摇摆不定,觉得我为您提供了一个可靠的答案。这里似乎对Swift中的值类型有些困惑。
首先,您的代码不会导致竞争条件,因为正如您在注释中所指出的那样,结构是值类型,因此按拷贝分配(也就是当分配给新变量时,将创建值类型的深层副本) )。这里的每个线程(队列)都将拥有分配给temp
的结构自己的线程本地副本,默认情况下,该副本可防止出现竞争情况(这有助于结构的线程安全)。永远不会从外部线程访问(或访问)它们。
也
即使在调用mutating方法时,我们也无法真正在Swift中对结构进行突变,因为创建了一个新方法,对吧?
不一定正确。来自Swift文档:
但是,如果您需要在特定方法中修改结构或枚举的属性,则可以选择对该方法的行为进行更改。然后,该方法可以从方法内部更改(即更改)其属性,并且在方法结束时将其所做的任何更改写回到原始结构。该方法还可以为其隐式的self属性分配一个全新的实例,该新实例将在方法结束时替换现有实例。
我的意思是有时实际上会对原始结构的值进行更改,并且在某些情况下可以为self分配一个全新的实例。这些都不能为多线程提供直接的“好处”,而仅仅是Swift中值类型的工作方式。实际上,“好处”来自“选择加入”到易变的行为。
“不变性有助于线程安全”的思想从何而来?
再次的价值类型强调了价值高于身份的重要性,因此我们应该能够对其价值做出一些可靠的假设。如果值类型默认情况下是可变的,则对值(及其值)的预测/假设将变得更加困难。如果某些东西是不可变的,则推理和做出假设变得容易得多。我们可以假定一个不变值在传递给它的任何线程中都是相同的。
如果我们必须“打开”可变性,则可以更轻松地查明值类型的更改来自何处,并有助于减少因意外更改值而产生的错误。此外,它仍然相对容易推理和对值进行预测,因为我们确切知道哪些方法可以进行更改,并且(希望)知道变异方法将在何种条件/情况下运行
这与我能想到的其他语言相反,例如C ++,您在其中暗示一种方法无法通过在方法标题的末尾添加关键字const
来更改成员的值。 / p>
如果我们不修改变量
a
(或者只需使用let使其成为常量),使用struct代替类的好处是什么,就线程安全而言,这么简单的结构?
如果我们要使用一个类,即使在诸如您所描述的那种简单情况下,那么即使使用let
分配也不足以防止产生副作用。它仍然是脆弱的,因为它是引用类型。即使在同一线程内,对类类型的let分配也不会停止副作用。
class StringSim{
var heldString:String
init(held: String){
heldString = held
}
func display(){
print(heldString)
}
func append(str: String){
heldString.append(str)
}
}
let myStr = StringSim(held:"Hello, playground")
var mySecStr = myStr
mySecStr.append(str: "foo")
myStr.display()
mySecStr.display()
此代码将产生以下输出:
Hello, playgroundfoo
Hello, playgroundfoo
将上述代码与使用Strings(Swift Standard结构类型)的类似操作进行比较,您将看到结果的差异。
TL; DR 对永不更改或仅在特定情况下只能更改的值进行预测要容易得多。
如果有一个更好的了解的人想纠正我的答案或指出我的错误之处,请放心。