Можно ли реализовать код с многопоточной чтением и однопоточной записью с блокирующей операцией в Swift
Что проверяют
Знаете ли вы паттерн «много читателей, один писатель» и умеете ли собрать его на GCD без гонки данных. Следом обычно спрашивают, как сделать то же самое в Swift Concurrency и почему actor тут не полная замена.
Как отвечать
Да, это классический readers-writer lock. Самый частый вариант на GCD:
- своя конкурентная очередь:
DispatchQueue(label: "cache", attributes: .concurrent). Чтения идут черезqueue.sync { ... }и выполняются параллельно; - запись с барьером:
queue.async(flags: .barrier) { ... }. Барьер ждёт завершения текущих чтений, выполняется в одиночку и только потом пускает следующих. Если запись должна блокировать вызывающий поток до конца, берутsync(flags: .barrier); pthread_rwlock_t: низкоуровневая блокировка чтения и записи, быстрее очереди, но требует аккуратной инициализации и стабильного адреса в памяти, поэтому её заворачивают в класс;- эксклюзивные замки:
NSLock,OSAllocatedUnfairLock(iOS 16),Mutexиз Synchronization (iOS 18) пускают одного владельца, и чтения тоже встают в очередь. На коротких операциях это часто быстрее и проще барьеров.
В Swift Concurrency actor сериализует все обращения, параллельных чтений у него нет. Зато нет и блокировки потоков: вызовы идут через await. Блокирующий sync или замок внутри async-кода опасен, пул потоков там кооперативный и маленький, а удерживать замок через await нельзя вовсе.
Хорошо, если скажете, когда такой паттерн оправдан: чтений много, запись редкая, критическая секция заметная. Для счётчика или небольшого словаря обычного замка хватает.
Где спотыкаются
Защищают чтение одной сущностью, а запись другой, например очередью и NSLock. Они друг о друге не знают, и чтение спокойно идёт в момент записи.
Ставят барьер на глобальную очередь. На DispatchQueue.global() флаг .barrier ничего не делает, работает он только на своей конкурентной очереди.
Вызывают sync(flags: .barrier) изнутри чтения на той же очереди. Барьер ждёт завершения чтения, чтение ждёт барьер, и приложение встаёт в дедлок.