Hi Jordan,

For which specific code sample(s) do you think it’s a bug? In the previous code 
samples, are there cases where you think a data race is to be expected?

Thanks.

> On Dec 6, 2017, at 12:05 AM, Jordan Rose <jordan_r...@apple.com> wrote:
> 
> I'm seeing the race too when compiling with -O (and TSan enabled). I'm 95% 
> sure this should be valid Swift code without any further synchronization, so 
> please file a bug at https://bugs.swift.org <https://bugs.swift.org/>. (But 
> I'm only 95% sure because concurrency is hard.)
> 
> Looking at the backtraces, it looks like one thread thinks it has exclusive 
> ownership of the buffer while the other thread is still copying things out of 
> it. This is a bug on Swift's side; this code should work. I'm pretty sure 
> this is actually a situation I was just talking about with Michael I from the 
> stdlib team a few days ago, so it's good to have a test case for it.
> 
> Jordan
> 
> 
>> On Dec 5, 2017, at 14:23, Romain Jacquinot via swift-users 
>> <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
>> 
>> Also, on the official Swift blog 
>> (https://developer.apple.com/swift/blog/?id=10 
>> <https://developer.apple.com/swift/blog/?id=10>), it is stated that:
>> 
>> "One of the primary reasons to choose value types over reference types is 
>> the ability to more easily reason about your code. If you always get a 
>> unique, copied instance, you can trust that no other part of your app is 
>> changing the data under the covers. This is especially helpful in 
>> multi-threaded environments where a different thread could alter your data 
>> out from under you.
>> […]
>> In Swift, Array, String, and Dictionary are all value types. They behave 
>> much like a simple int value in C, acting as a unique instance of that data. 
>> You don’t need to do anything special — such as making an explicit copy — to 
>> prevent other code from modifying that data behind your back. Importantly, 
>> you can safely pass copies of values across threads without synchronization. 
>> In the spirit of improving safety, this model will help you write more 
>> predictable code in Swift.”
>> 
>> Yet, data race can occur here:
>> 
>> public class MyClass {
>> 
>>     private var _myArray: Array<Int> = []
>>     private var _lock = NSLock()
>> 
>>     public var myArray: Array<Int> {
>>         lock.lock()
>>         defer {
>>             lock.unlock()
>>         }
>>         let copy = _myArray
>>         return copy
>>     }
>> 
>>     public func doSomethingThatMutatesArray() {
>>         _lock.lock()
>>         _myArray.append(1) // data race here: write of size 8 by thread 1
>>         _lock.unlock()
>>     }
>> }
>> 
>> 
>> let myClass = MyClass()
>> 
>> func mutateArray() {
>>     myClass.doSomethingThatMutatesArray()
>>     var arrayCopy = myClass.myArray
>>     arrayCopy.append(2) // data race here: read of size 8 by thread 10
>> }
>> 
>> let q1 = DispatchQueue(label: "testQ1")
>> let q2 = DispatchQueue(label: "testQ2")
>> let q3 = DispatchQueue(label: "testQ3")
>> 
>> let iterations = 100_000
>> 
>> q1.async {
>>     for _ in 0..<iterations {
>>         mutateArray()
>>     }
>> }
>> 
>> q2.async {
>>     for _ in 0..<iterations {
>>         mutateArray()
>>     }
>> }
>> 
>> q3.async {
>>     for _ in 0..<iterations {
>>         mutateArray()
>>     }
>> }
>> 
>> for _ in 0..<iterations {
>>      mutateArray()
>> }
>> 
>> q1.sync {}
>> q2.sync {}
>> q3.sync {}
>> NSLog("done")
>> 
>> It also can occur for instance if I replace the mutateArray() function with 
>> the following method:
>> 
>> func enumerateArray() {
>>     myClass.doSomethingThatMutatesArray()
>>     for element in myClass.myArray {  // data race here: read of size 8 by 
>> thread 5
>>         let _ = element
>>     }
>> }
>> 
>> How can I return a “predictable” copy from the MyClass.myArray getter, so 
>> that I can later mutate the copy without synchronization like in the 
>> mutateArray() function?
>> 
>>> On Dec 5, 2017, at 9:23 PM, Romain Jacquinot via swift-users 
>>> <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
>>> 
>>> Hi Jens,
>>> 
>>> In the SynchronizedArray class, I use a lock to perform mutating operations 
>>> on the array. However, my questions concern the case where an array is 
>>> exposed as a public property of a thread-safe class, like this:
>>> 
>>> public class MyClass {
>>> 
>>>     private var _myArray: Array<Int> = []
>>>     private var _lock = NSLock()
>>> 
>>>     public var myArray: Array<Int> {
>>>             _lock.lock()
>>>             defer { _lock.unlock() }
>>>             return _myArray
>>>     }
>>> 
>>>     public func doSomethingThatMutatesArray() {
>>>             _lock.lock()
>>>             _myArray.append(1)
>>>             _lock.unlock()
>>>     }
>>> }
>>> 
>>> Arrays are implemented as structs in Swift. This is a value type, but 
>>> because Array<T> implements copy-on-write, there is an issue if you do 
>>> something like this from multiple threads:
>>> 
>>> let myClass = MyClass()
>>> 
>>> func callFromMultipleThreads() {
>>>     let arrayCopy = myClass.myArray
>>>     arrayCopy.append(2) // race condition here
>>> }
>>> 
>>> This is quite problematic, because the user of MyClass shouldn’t have to 
>>> worry about side-effects when using a copy of the value from myArray.
>>> 
>>>> On Dec 5, 2017, at 8:22 PM, Jens Alfke <j...@mooseyard.com 
>>>> <mailto:j...@mooseyard.com>> wrote:
>>>> 
>>>> 
>>>> 
>>>>> On Dec 5, 2017, at 6:24 AM, Michel Fortin via swift-users 
>>>>> <swift-users@swift.org <mailto:swift-users@swift.org>> 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 think you’ve misunderstood. The race condition Romain is referring to is 
>>>> when the two threads both access the shared storage, through separate 
>>>> array variables.
>>>> 
>>>> Romain:
>>>> From the thread sanitizer warnings, it sounds like the implementation of 
>>>> Array is not using synchronization around its call(s) to 
>>>> isKnownUniquelyReferenced … which would mean the class is not thread-safe.
>>>> 
>>>> It’s pretty common for the regular (mutable) collection classes supplied 
>>>> by a framework to be non-thread-safe; for example consider Foundation and 
>>>> Java. The reason is that the overhead of taking a lock every time you 
>>>> access an array element is pretty high. Generally it’s preferable to use 
>>>> larger-granularity locks, i.e. grab an external lock before performing a 
>>>> number of array operations.
>>>> 
>>>> —Jens
>>> 
>>> _______________________________________________
>>> swift-users mailing list
>>> swift-users@swift.org <mailto:swift-users@swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-users 
>>> <https://lists.swift.org/mailman/listinfo/swift-users>
>> 
>> _______________________________________________
>> swift-users mailing list
>> swift-users@swift.org <mailto:swift-users@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-users
> 

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Reply via email to