[swift-users] Fwd: Data races with copy-on-write
Romain Jacquinot
rjacquinot at me.com
Tue Dec 5 06:56:24 CST 2017
Small typo in the second sample code. Should read:
return try closure(&_elements)
> Begin forwarded message:
>
> From: Romain Jacquinot via swift-users <swift-users at swift.org>
> Subject: [swift-users] Data races with copy-on-write
> Date: December 5, 2017 at 11:20:46 AM GMT+1
> To: swift-users at swift.org
> Reply-To: Romain Jacquinot <rjacquinot at me.com>
>
> Hi,
>
> I'm trying to better understand how copy-on-write works, especially in a multithreaded environment, but there are a few things that confuse me.
>
> From the documentation, it is said that:
> "If the instance passed as object is being accessed by multiple threads simultaneously, isKnownUniquelyReferenced(_:) may still return true. Therefore, you must only call this function from mutating methods with appropriate thread synchronization. That will ensure that isKnownUniquelyReferenced(_:) only returns true when there is really one accessor, or when there is a race condition, which is already undefined behavior."
>
> Let's consider this sample code:
>
> func mutateArray(_ array: [Int]) {
> var elements = array
> elements.append(1)
> }
>
> let q1 = DispatchQueue(label: "testQ1")
> let q2 = DispatchQueue(label: "testQ2")
> let q3 = DispatchQueue(label: "testQ3")
>
> let iterations = 1000
>
> var array: [Int] = [1, 2, 3]
>
> q1.async {
> for _ in 0..<iterations {
> mutateArray(array)
> }
> }
>
> q2.async {
> for _ in 0..<iterations {
> mutateArray(array)
> }
> }
>
> q3.async {
> for _ in 0..<iterations {
> mutateArray(array)
> }
> }
>
> // ...
>
> From what I understand, since Array<T> implements copy-on-write, the array should be copied only when the mutating append(_:) method is called in mutateArray(_:). Therefore, isKnownUniquelyReferenced(_:) should be called to determine whether a copy is required or not.
>
> However, it is being accessed by multiple threads simultaneously, which may cause a race condition, right? So why does the thread sanitizer never detect a race condition here? Is there some compiler optimization going on here?
>
> On the other hand, the thread sanitizer always detects a race condition, for instance, if I add the following code to mutate directly the array:
>
> for _ in 0..<iterations {
> array.append(1)
> }
>
> In this case, is it because I mutate the array buffer that is being copied from other threads?
>
>
> Even strangier, let's consider the following sample code:
>
> class SynchronizedArray<Element> {
>
> // [...]
>
> private var lock = NSLock()
> private var _elements: Array<Element>
>
> var elements: Array<Element> {
> lock.lock()
> defer { lock.unlock() }
> return _elements
> }
>
> @discardableResult
> public final func access<R>(_ closure: (inout T) throws -> R) rethrows -> R {
> lock.lock()
> defer { lock.unlock() }
> return try closure(&_value)
> }
> }
>
> let syncArray = SynchronizedArray<Int>()
>
> func mutateArray() {
> syncArray.access { array in
> array.append(1)
> }
>
> var elements = syncArray.elements
> var copy = elements // [X] no race condition detected by TSan when I add this line
> elements.append(1) // race condition detected by TSan (if previous line is missing)
> }
>
> // Call mutateArray() from multiple threads like in the first sample code.
>
> The line marked with [X] does nothing useful, yet adding this line prevents the race condition at the next line to be detected by the thread sanitizer. Is this again because of some compiler optimization?
>
> However, when the array buffer is being copied, we can mutate the same buffer with the append(_:) method, right? So, shouldn't the thread sanitizer detect a race condition here?
>
> Please let me know if I ever misunderstood how copy-on-write works in Swift.
>
> Also, I'd like to know:
> - besides capture lists, what are the correct ways to pass a copy-on-write value between threads?
> - for thread-safe classes that expose an array as a property, should I always copy the private array variable before returning it from the public getter? If so, is there any recommended way to force-copy a value type in Swift ?
>
> Any help would be greatly appreciated.
> Thanks.
>
> Note: I'm using Swift 4 with the latest Xcode version (9.2 (9C40b)) and the thread sanitizer enabled.
>
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20171205/f89f7b90/attachment.html>
More information about the swift-users
mailing list