Xcode неправильно сообщает о состоянии гонки Swift Access

Я считаю, что XCode неправильно сообщает о гонке Swift Access Race в моем SynchronizedDictionary - или это так?

Мой SynchronizedDictionary выглядит так:

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
            }
        }
    }
}

Следующий тестовый код вызовет проблему «Swift Access Race» (когда для схемы включено средство Thread Sanitizer):

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)

Доступ к Swift Race выглядит следующим образом:

Быстрый доступ к гонке Я действительно не ожидал, что здесь будет состояние гонки за доступ, потому что SynchronizedDictionary должен обрабатывать параллелизм.

Я могу исправить проблему, в тесте обернув получение и настройку в DispatchQueue, подобно фактической реализации 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)

... но это уже происходит внутри SynchronizedDictionary - так почему же Xcode сообщает о состоянии гонки за доступ? - виноват ли Xcode или я что-то упустил?


person Pelle Stenild Coltau    schedule 05.03.2019    source источник
comment
Ваши 2 асинхронных блока могут быть назначены разным потокам, контролируемым вашей параллельной очередью. Любой из этих потоков может выполняться первым. Я подозреваю, что поэтому он жалуется на то, что код не гарантирует, что сеттер будет запущен раньше, чем геттер. Вы можете проверить мою теорию, создав последовательную очередь и посмотрев, исчезнет ли ошибка.   -  person Phillip Mills    schedule 07.03.2019
comment
Спасибо за ваш отзыв. Я согласен с вами, что замена параллельной очереди последовательной решит проблему. Однако это противоречит цели SynchronizedDictionary - возможности одновременного доступа к нему.   -  person Pelle Stenild Coltau    schedule 20.03.2019
comment
@PhillipMills В конце концов, и установщик, и получатель попадают в параллельную очередь, а установщик все блокирует. Я не понимаю, как это приводит к условиям гонки, когда все заблокировано и ждет завершения установки.   -  person J. Doe    schedule 26.03.2019
comment
@PelleStenildColtau: обратите внимание, что в установщике вы можете отправлять асинхронно (с барьером), см., например, medium.com/@oyalhi/dispatch-barriers-in-swift-3-6c4a295215d6.   -  person Martin R    schedule 28.03.2019


Ответы (1)


Дезинфектор потоков сообщает о гонке за доступ к Swift

var syncDict = SynchronizedDictionary<String, String>()

структура, потому что есть изменяющий доступ (через установщик индекса) в

syncDict["\(i)"] = "\(i)"

из одного потока и доступ только для чтения к той же структуре (через геттер индекса) в

_ = syncDict["\(i)"]

из другого потока, без синхронизации.

Это не имеет ничего общего с конфликтующим доступом к свойству private var dictionary или вообще с тем, что происходит внутри методов индекса. Вы получите ту же «гонку за быстрый доступ», если упростите структуру до

public struct SynchronizedDictionary<K: Hashable, V> {
    private let dummy = 1

    public subscript(key: String) -> String {
        get {
            return key
        }
        set {
        }
    }
}

Итак, это правильный отчет от дезинфицирующего средства потока, а не ошибка.

Возможным решением было бы вместо этого определить класс:

public class SynchronizedDictionary<K: Hashable, V> { ... }

Это ссылочный тип, и установщик индекса больше не изменяет переменную syncDict (которая теперь является «указателем» на реальное хранилище объектов). С этим изменением ваш код работает без ошибок.

person Martin R    schedule 27.03.2019
comment
Ах, так это состояние гонки для переменной syncDict, и это вообще не имеет ничего общего с тем, что находится внутри syncDict :) - person J. Doe; 28.03.2019