Ответ
Потокобезопасный массив гарантирует корректность данных при конкурентном доступе из нескольких потоков. В Swift для этого можно использовать GCD (Grand Central Dispatch) с барьерными операциями.
Основной подход: Использовать DispatchQueue с атрибутом .concurrent.
- Запись выполняется асинхронно с флагом
.barrier, что делает её эксклюзивной. - Чтение выполняется синхронно для получения актуальных данных.
class ThreadSafeArray<Element> {
private var array: [Element] = []
private let queue = DispatchQueue(label: "threadSafe.array.queue", attributes: .concurrent)
func append(_ element: Element) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
var elements: [Element] {
return queue.sync {
return self.array
}
}
}
Почему это работает? Барьерная задача (flags: .barrier) ждёт завершения всех текущих задач в очереди и выполняется в одиночку, блокируя другие задачи на время записи. Это обеспечивает безопасность записи. Синхронное чтение (queue.sync) гарантирует, что возвращаемые данные будут соответствовать состоянию на момент вызова.
Альтернативы и нюансы:
- Для сценариев с интенсивной записью или более тонкого контроля можно использовать низкоуровневые примитивы:
NSLock,os_unfair_lockилиpthread_rwlock_t. - Реализация выше оптимизирована для частого чтения и редкой записи. Для полного набора операций (удаление, индексация) потребуется добавить аналогичные потокобезопасные методы.
Ответ 18+ 🔞
А, ну ты глянь, классика жанра — потокобезопасный массив! Это ж когда несколько потоков лезут в одну кучу данных, как пьяные в подъезд, и надо, чтобы они друг другу мозги не вынесли и не начитались хуйни вместо нужных данных.
Вот смотри, в Swift за это обычно отвечает GCD, этакая палочка-выручалочка для распиздяйства между потоками. Основная фишка — использовать очередь (DispatchQueue), но не простую, а конкурентную (.concurrent).
Суть проста, как три копейки:
- Когда пишешь — делаешь это асинхронно, но с волшебным флажком
.barrier. Это как крикнуть: «Все нахуй с дороги, я сейчас один буду делать!». Пока эта операция не закончится, все остальные потоки стоят и курят в сторонке. - Когда читаешь — делаешь это синхронно (
sync). Это чтобы сразу, на месте, получить актуальные данные, а не какую-то левую версию из прошлой жизни массива.
Вот, смотри, как это выглядит в коде, блядь:
class ThreadSafeArray<Element> {
private var array: [Element] = []
private let queue = DispatchQueue(label: "threadSafe.array.queue", attributes: .concurrent)
func append(_ element: Element) {
queue.async(flags: .barrier) {
self.array.append(element)
}
}
var elements: [Element] {
return queue.sync {
return self.array
}
}
}
А почему, спрашивается, это работает? Всё гениально и просто, ёпта! Барьерная задача — это такой увалень, который говорит: «Я подожду, пока все эти мелкие читающие потоки свои дела закончат, а потом буду один, как царь, писать. И пока я пишу — все молчат, блядь!». Это и даёт безопасность записи. А синхронное чтение — это просто чтобы не улететь в будущее или прошлое, а получить данные именно сейчас, какие они есть.
Но есть нюансы, конечно, куда ж без них:
- Если у тебя там адская писанина, а не чтение, то вся эта барьерная магия может начать тормозить, как старая кобыла. Тогда уже надо лезть в дебри с низкоуровневыми примитивами:
NSLock,os_unfair_lockили вот эту страшную штукуpthread_rwlock_t. Это уже для настоящих гурманов боли. - Реализация выше — она для типичного случая «часто читаем, редко пишем». Если тебе надо удалять, искать по индексу или ещё какую дичь творить — придётся каждый метод оборачивать в эту же защитную обёртку, иначе будет пиздец и рассинхрон.
Короче, идея в том, чтобы организовать эту очередь, а не позволять потокам драться за массив, как собаки за кость.