Creo que XCode informa incorrectamente Swift Access Race en mi SynchronizedDictionary , ¿o no?
Mi SynchronizedDictionary se ve así:
public struct SynchronizedDictionary<K: Hashable, V> { private var dictionary = [K: V]() private let queue = DispatchQueue( label: "SynchronizedDictionary", qos: DispatchQoS.userInitiated, attributes: [DispatchQueue.Attributes.concurrent] ) public subscript(key: K) -> V? { get { return queue.sync { return self.dictionary[key] } } mutating set { queue.sync(flags: .barrier) { self.dictionary[key] = newValue } } } }El siguiente código de prueba activará un problema de "Carrera de acceso rápido" (cuando Thread Sanitizer esté activado para el esquema):
var syncDict = SynchronizedDictionary<String, String>() let setExpectation = XCTestExpectation(description: "set_expectation") let getExpectation = XCTestExpectation(description: "get_expectation") let queue = DispatchQueue(label: "SyncDictTest", qos: .background, attributes: [.concurrent]) queue.async { for i in 0...100 { syncDict["\(i)"] = "\(i)" } setExpectation.fulfill() } queue.async { for i in 0...100 { _ = syncDict["\(i)"] } getExpectation.fulfill() } self.wait(for: [setExpectation, getExpectation], timeout: 30)El Swift Race Access se ve así:
Realmente no esperaba que hubiera una condición de carrera de acceso aquí, porque SynchronizedDictionary debería manejar la concurrencia.
Puedo solucionar el problema, en la prueba, envolviendo la obtención y configuración en un DispatchQueue similar a la implementación real de SynchronizedDictionary :
let accessQueue = DispatchQueue( label: "AccessQueue", qos: DispatchQoS.userInitiated, attributes: [DispatchQueue.Attributes.concurrent] ) var syncDict = SynchronizedDictionary<String, String>() let setExpectation = XCTestExpectation(description: "set_expectation") let getExpectation = XCTestExpectation(description: "get_expectation") let queue = DispatchQueue(label: "SyncDictTest", qos: .background, attributes: [.concurrent]) queue.async { for i in 0...100 { accessQueue.sync(flags: .barrier) { syncDict["\(i)"] = "\(i)" } } setExpectation.fulfill() } queue.async { for i in 0...100 { accessQueue.sync { _ = syncDict["\(i)"] } } getExpectation.fulfill() } self.wait(for: [setExpectation, getExpectation], timeout: 30) ... pero eso ya sucede dentro de SynchronizedDictionary , entonces, ¿por qué Xcode informa una condición de carrera de acceso? - ¿Xcode tiene la culpa o me estoy perdiendo algo?
El desinfectante de subprocesos informa una carrera de acceso Swift al
var syncDict = SynchronizedDictionary<String, String>()estructura, porque hay un acceso mutante (a través del setter subíndice) en
syncDict["\(i)"] = "\(i)"de un subproceso, y un acceso de solo lectura a la misma estructura (a través del captador de subíndices) en
_ = syncDict["\(i)"]de un hilo diferente, sin sincronización.
Esto no tiene nada que ver con el acceso en conflicto a la propiedad private var dictionary , o con lo que sucede dentro de los métodos de subíndice. Obtendrá la misma "carrera de acceso rápido" si simplifica la estructura para
public struct SynchronizedDictionary<K: Hashable, V> { private let dummy = 1 public subscript(key: String) -> String { get { return key } set { } } }Entonces, este es un informe correcto del desinfectante de hilos, no un error.
Una posible solución sería definir una clase en su lugar:
public class SynchronizedDictionary<K: Hashable, V> { ... } Ese es un tipo de referencia y el setter de subíndice ya no muta la variable syncDict (que ahora es un "puntero" en el almacenamiento de objetos real). Con ese cambio, su código se ejecuta sin errores.