8

在这种情况下我需要某种显式同步吗?

class A { 
  let val: Int; 
  init(_ newVal: Int) { 
    val = newVal
   }
}

public class B {
  var a: A? = nil
  public func setA() { a = A(0) }
  public func hasA() -> Bool { return a != nil }
}

B类中还有另一种方法:

public func resetA() {
  guard hasA() else { return }
  a = A(1)
}

setA()并且resetA()可以从任何线程以任何顺序调用。

我知道可能存在竞争条件,即如果同时一个线程调用setA()和另一个线程调用resetA(),则结果不确定:val将是0,或1,但我不在乎:无论如何,hasA()将返回 true,不会'是吗?

如果Astruct而不是class,答案会改变吗?

4

1 回答 1

2

简而言之,不,属性访问器不是原子的。请参阅 WWDC 2016 视频Concurrent Programming With GCD in Swift 3,其中讨论了该语言中缺乏原子/同步本机。(这是一个 GCD 演讲,所以当他们随后深入研究同步方法时,他们专注于 GCD 方法,但任何同步方法都可以。)Apple 在自己的代码中使用了各种不同的同步方法。例如,ThreadSafeArrayStore他们使用他们使用NSLock)。


如果与锁同步,我可能会建议如下扩展:

extension NSLocking {
    func synchronized<T>(block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

Apple 在他们自己的代码中使用了这种模式,尽管他们碰巧称之为它withLock而不是synchronized. 但模式是一样的。

然后你可以这样做:

public class B {
    private var lock = NSLock()
    private var a: A?             // make this private to prevent unsynchronized direct access to this property

    public func setA() {
        lock.synchronized {
            a = A(0)
        }
    }

    public func hasA() -> Bool {
        lock.synchronized {
            a != nil
        }
    }

    public func resetA() {
        lock.synchronized {
            guard a != nil else { return }
            a = A(1)
        }
    }
}

也许

public class B {
    private var lock = NSLock()
    private var _a: A?

    public var a: A? {
        get { lock.synchronized { _a } }
        set { lock.synchronized { _a = newValue } }
    }

    public var hasA: Bool {
        lock.synchronized { _a != nil }
    }

    public func resetA() {
        lock.synchronized {
            guard _a != nil else { return }
            _a = A(1)
        }
    }
}

我承认在暴露时有些不安hasA,因为它实际上会邀请应用程序开发人员这样写:

if !b.hasA {
    b.a = ...
}

就防止同时访问内存而言,这很好,但是如果两个线程同时执行它会引入逻辑竞争,两个线程都恰好通过了!hasA测试,并且它们都替换了值,最后一个获胜。

相反,我可能会为我们编写一个方法来执行此操作:

public class B {
    private var lock = NSLock() // replacing os_unfair_lock_s()
    private var _a: A? = nil // fixed, thanks to Rob

    var a: A? {
        get { lock.synchronized { _a } }
        set { lock.synchronized { _a = newValue } }
    }

    public func withA(block: (inout A?) throws -> T) rethrows -> T {
        try lock.synchronized {
            try block(&_a)
        }
    }
}

这样你就可以做到:

b.withA { a in
    if a == nil {
        a = ...
    }
}

这是线程安全的,因为我们让调用者在一个同步步骤中包装所有逻辑任务(检查是否anil,如果是,则初始化)。a这是一个很好的通用解决方案。它可以防止逻辑竞赛。


现在上面的例子太抽象了,很难理解。所以让我们考虑一个实际的例子,A​​pple 的一个变体ThreadSafeArrayStore

public class ThreadSafeArrayStore<Value> {
    private var underlying: [Value]
    private let lock = NSLock()

    public init(_ seed: [Value] = []) {
        underlying = seed
    }

    public subscript(index: Int) -> Value {
        get { lock.synchronized { underlying[index] } }
        set { lock.synchronized { underlying[index] = newValue } }
    }

    public func get() -> [Value] {
        lock.synchronized {
            underlying
        }
    }

    public func clear() {
        lock.synchronized {
            underlying = []
        }
    }

    public func append(_ item: Value) {
        lock.synchronized {
            underlying.append(item)
        }
    }

    public var count: Int {
        lock.synchronized {
            underlying.count
        }
    }

    public var isEmpty: Bool {
        lock.synchronized {
            underlying.isEmpty
        }
    }

    public func map<NewValue>(_ transform: (Value) throws -> NewValue) rethrows -> [NewValue] {
        try lock.synchronized {
            try underlying.map(transform)
        }
    }

    public func compactMap<NewValue>(_ transform: (Value) throws -> NewValue?) rethrows -> [NewValue] {
        try lock.synchronized {
            try underlying.compactMap(transform)
        }
    }
}

这里我们有一个同步数组,我们定义一个接口以线程安全的方式与底层数组交互。


或者,如果您想要一个更简单的示例,请考虑使用线程安全对象来跟踪最高的项目是什么。我们不会有一个hasValue布尔值,而是将它合并到我们的同步updateIfTaller方法中:

public class Tallest {
    private var _height: Float?
    private let lock = NSLock()

    var height: Float? {
        lock.synchronized { _height }
    }

    func updateIfTaller(_ candidate: Float) {
        lock.synchronized {
            guard let tallest = _height else {
                _height = candidate
                return
            }

            if candidate > tallest {
                _height = candidate
            }
        }
    }
}

只是几个例子。希望它能说明这个想法。

于 2021-01-22T16:36:22.560 回答