<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class="">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.<div class=""><br class=""></div><div class="">I'm not sure what is the problem with your SynchronizedArray example. But I would try reimplementing `var element` this way:</div><div class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var elements: Array<Element> { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return access { $0 }</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} </div><div class=""><br class=""></div><div class="">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.<br class=""><div class=""><div><br class=""><blockquote type="cite" class=""><div class="">Le 5 déc. 2017 à 5:20, Romain Jacquinot via swift-users <<a href="mailto:swift-users@swift.org" class="">swift-users@swift.org</a>> a écrit :</div><br class="Apple-interchange-newline"><div class=""><div class="">Hi,<br class=""><br class="">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.<br class=""><br class="">From the documentation, it is said that:<br class="">"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."<br class=""><br class="">Let's consider this sample code:<br class=""><br class="">func mutateArray(_ array: [Int]) { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var elements = array <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>elements.append(1) <br class="">} <br class=""><br class="">let q1 = DispatchQueue(label: "testQ1") <br class="">let q2 = DispatchQueue(label: "testQ2") <br class="">let q3 = DispatchQueue(label: "testQ3") <br class=""><br class="">let iterations = 1000 <br class=""><br class="">var array: [Int] = [1, 2, 3] <br class=""><br class="">q1.async { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>for _ in 0..<iterations { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>mutateArray(array) <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} <br class="">} <br class=""><br class="">q2.async { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>for _ in 0..<iterations { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>mutateArray(array) <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} <br class="">} <br class=""><br class="">q3.async { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>for _ in 0..<iterations { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>mutateArray(array) <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} <br class="">} <br class=""><br class="">// ... <br class=""><br class="">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.<br class=""><br class="">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?<br class=""><br class="">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:<br class=""><br class="">for _ in 0..<iterations { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>array.append(1) <br class="">} <br class=""><br class="">In this case, is it because I mutate the array buffer that is being copied from other threads?<br class=""><br class=""><br class="">Even strangier, let's consider the following sample code:<br class=""><br class="">class SynchronizedArray<Element> { <br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// [...] <br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>private var lock = NSLock() <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>private var _elements: Array<Element> <br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var elements: Array<Element> { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>lock.lock() <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>defer { lock.unlock() } <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return _elements <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>@discardableResult <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>public final func access<R>(_ closure: (inout T) throws -> R) rethrows -> R { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>lock.lock() <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>defer { lock.unlock() } <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return try closure(&_value) <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} <br class="">} <br class=""><br class="">let syncArray = SynchronizedArray<Int>() <br class=""><br class="">func mutateArray() { <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>syncArray.access { array in <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>array.append(1) <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>} <br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var elements = syncArray.elements <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var copy = elements // [X] no race condition detected by TSan when I add this line <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>elements.append(1) // race condition detected by TSan (if previous line is missing) <br class="">} <br class=""><br class="">// Call mutateArray() from multiple threads like in the first sample code. <br class=""><br class="">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?<br class=""><br class="">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?<br class=""><br class="">Please let me know if I ever misunderstood how copy-on-write works in Swift.<br class=""><br class="">Also, I'd like to know:<br class="">- besides capture lists, what are the correct ways to pass a copy-on-write value between threads?<br class="">- 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 ?<br class=""><br class="">Any help would be greatly appreciated.<br class="">Thanks.<br class=""><br class="">Note: I'm using Swift 4 with the latest Xcode version (9.2 (9C40b)) and the thread sanitizer enabled.<br class=""><br class="">_______________________________________________<br class="">swift-users mailing list<br class=""><a href="mailto:swift-users@swift.org" class="">swift-users@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-users<br class=""></div></div></blockquote></div><br class=""></div></div><br class=""><br class=""><div class="">
<div style="color: rgb(0, 0, 0); letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div style="color: rgb(0, 0, 0); letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div style="color: rgb(0, 0, 0); letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div style="color: rgb(0, 0, 0); font-family: Helvetica; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><span class="Apple-style-span" style="border-collapse: separate; color: rgb(0, 0, 0); font-family: Helvetica; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; border-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-stroke-width: 0px;"><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><span class="Apple-style-span" style="border-collapse: separate; color: rgb(0, 0, 0); font-family: Helvetica; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; border-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-stroke-width: 0px;"><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><span class="Apple-style-span" style="border-collapse: separate; color: rgb(0, 0, 0); font-family: Helvetica; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; border-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-stroke-width: 0px;"><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">-- <br class="">Michel Fortin</div><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><span style="text-align: -webkit-auto;" class=""><a href="https://michelf.ca" class="">https://michelf.ca</a></span></div></span></div></span></div></span></div></div></div></div>
</div>
<br class=""></body></html>