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

Reply via email to