[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