Thank you Michel. > Rather, use a different copy of the variable to each thread.
How should I copy a variable to each thread? I’m not sure to understand what you mean by “copy” in this case. If I have this: var a = Array<Int>() var b = a do you consider “b” as being a copy of the variable “a”? > Copied variables will share the same storage but will make a copy of the > storage when writing to it. But by sharing the same storage and making a copy of the storage when writing to it, there would be a race condition if the storage is being written by another thread while being copied, right? > I'm not sure what is the problem with your SynchronizedArray example. But I > would try reimplementing `var element` this way: Reimplementing the elements property doesn’t fix the race. Of course, for my specific example, I can still do: var elements: Array<Element> { return access { array in array.map { $0 } } } but I’d like to know if there is a more generic way to copy a value type variable in Swift. > On Dec 5, 2017, at 3:23 PM, Michel Fortin <michel.for...@michelf.ca> wrote: > > The array *storage* is copy on write. The array variable (which essentially > contains a pointer to the storage) is not copy on write. If you refer to the > same array variable from multiple threads, you have a race. Rather, use a > different copy of the variable to each thread. Copied variables will share > the same storage but will make a copy of the storage when writing to it. > > I'm not sure what is the problem with your SynchronizedArray example. But I > would try reimplementing `var element` this way: > > var elements: Array<Element> { > return access { $0 } > } > > If this change fixes the race it means the compiler is making the copy after > the `unlock()` with your previous code, which could explain the detected > race. I suppose the additional copy afterwards fixes the race because of an > internal implementation detail (like changing the reference count for the > storage). I'd be wary of the optimizer breaking this trick though. > >> Le 5 déc. 2017 à 5:20, Romain Jacquinot via swift-users >> <swift-users@swift.org <mailto:swift-users@swift.org>> a écrit : >> >> 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@swift.org <mailto:swift-users@swift.org> >> https://lists.swift.org/mailman/listinfo/swift-users > > > > -- > Michel Fortin > https://michelf.ca <https://michelf.ca/>
_______________________________________________ swift-users mailing list swift-users@swift.org https://lists.swift.org/mailman/listinfo/swift-users