Асинхронное объединение двух словарей в Swift

#swift #multithreading #dictionary

#swift #многопоточность #словарь

Вопрос:

У меня возникла проблема с асинхронным объединением двух словарей. Идея состоит в том, чтобы выполнить функцию вычисления, которая возвращает словарь несколько раз асинхронно, а затем объединить результаты в один словарь. Я попытался выполнить следующее:

 var subResult: [String: Result] = [:]
let stride = (max_val - min_val) / 2   1
            
DispatchQueue.concurrentPerform(iterations: stride) { index in
    let c_size = max_val   2*index
    var s_size = min_provided
                
    var localResult: [String: Result] = [:]
    repeat {                
        let res = SubFlow().process(data: data, cSize: c_size, sSize: s_size)
                    
        localResult.merge( res  ) { (current, _) in current }
        s_size  = 2
    } while (s_size <= c_size)
                
    subResult.merge( localResult ) { (current, _) in current }
}
  

Это решение работает, но я не считаю его надежным, поскольку мы изменяем словарь асинхронно. Я новичок в Swift и не уверен, как я могу реализовать «безопасное» и эффективное слияние в этом случае?

Ответ №1:

Поскольку словари в Swift не являются потокобезопасными, вам необходимо убедиться, что все записи в словарь происходят в одной очереди.

Вы можете достичь этого либо путем создания последовательной очереди, либо путем создания параллельной очереди, убедившись при этом, что операции записи выполняются с барьерами. Последний подход позволит выполнять одновременное чтение из объекта, пока он не записывается в:

 var subResult: [String: Int] = [:]
let resultUpdateQueue = DispatchQueue(label: "com.example.myapp.resultUpdateQueue", attributes: .concurrent)
            
DispatchQueue.concurrentPerform(iterations: 10) { index in
    let localResult = ["sample(index)":index]
    resultUpdateQueue.sync(flags: .barrier) {
        subResult.merge( localResult ) { (current, _) in current }
    }
}

print(subResult)
  

Убедитесь, что не выполняете .concurrentPerform() в основной очереди, потому что она будет ждать, пока не завершатся все итерации.

Ответ №2:

Нет, это небезопасно. Это неопределенное поведение, и я несколько удивлен, что оно работает.

Вместо этого вы должны генерировать промежуточные результаты параллельно, а затем объединять их последовательно. Что-то вроде:

 DispatchQueue.concurrentPerform(iterations: stride) { index in 
    // ... call process and generated localResults ...

    serialQueue.dispatchAsync { subResult.merge ... }
}