← Назад к вопросам

Безопасно ли наполнять массив через async/await?

2.0 Middle🔥 72 комментариев
#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Безопасность наполнения массива через async/await

Нет, напрямую наполнять общий массив через async/await небезопасно, так как это приводит к состоянию гонки (race condition) и недетерминированному поведению. Swift массивы (как и большинство стандартных коллекций) не являются потокобезопасными по умолчанию. Асинхронные задачи выполняются в конкурентном контексте, и если несколько задач одновременно пытаются модифицировать один массив, это вызовет креши, некорректные данные или неожиданные утечки памяти.

Основные проблемы

  1. Состояние гонки: Когда несколько задач одновременно читают и записывают массив без синхронизации, окончательное состояние массива непредсказуемо.
  2. Повреждение памяти: Одновременные модификации могут нарушить внутреннюю структуру массива, приводя к крешам (например, EXC_BAD_ACCESS).
  3. Инвалидация индексов: При одновременном добавлении/удалении элементов индексы могут стать недействительными.
  4. Неатомарные операции: Даже append(_:) не является атомарной операцией — она включает несколько шагов (проверка емкости, копирование элемента, обновление счетчика).

Пример небезопасного кода

var unsafeArray: [Int] = []

func fillArrayUnsafe() async {
    await withTaskGroup(of: Void.self) { group in
        for i in 1...1000 {
            group.addTask {
                // МНОГОПОТОЧНАЯ МОДИФИКАЦИЯ - ОПАСНО!
                unsafeArray.append(i)
            }
        }
    }
}

// При вызове может произойти креш или массив получит меньше 1000 элементов

Безопасные подходы

1. Использование актора (Actor)

Наиболее современный и рекомендуемый способ в Swift.

actor SafeArrayStorage {
    private var array: [Int] = []
    
    func append(_ value: Int) {
        array.append(value)
    }
    
    func getAll() -> [Int] {
        return array
    }
}

func fillArraySafe() async {
    let storage = SafeArrayStorage()
    
    await withTaskGroup(of: Void.self) { group in
        for i in 1...1000 {
            group.addTask {
                await storage.append(i) // Автоматическая синхронизация через актор
            }
        }
    }
    
    let result = await storage.getAll()
    print("Безопасно добавлено \(result.count) элементов")
}

2. Использование очереди (DispatchQueue)

Классический подход с Grand Central Dispatch.

class ThreadSafeArray<T> {
    private var array: [T] = []
    private let queue = DispatchQueue(label: "com.example.threadsafe.array", attributes: .concurrent)
    
    func append(_ element: T) {
        queue.async(flags: .barrier) {
            self.array.append(element)
        }
    }
    
    var values: [T] {
        return queue.sync {
            return self.array
        }
    }
}

3. Использование NSLock или семофоров

Более низкоуровневый контроль.

class LockedArray<T> {
    private var array: [T] = []
    private let lock = NSLock()
    
    func append(_ element: T) {
        lock.lock()
        defer { lock.unlock() }
        array.append(element)
    }
}

4. Изоляция с помощью @MainActor

Если массив должен обновляться только в главном потоке.

@MainActor
class MainThreadArray {
    private var array: [Int] = []
    
    func append(_ value: Int) {
        array.append(value) // Гарантированно выполняется на главном потоке
    }
}

Рекомендации по выбору подхода

  1. Для нового кода на Swift 5.5+ используйте акторы — они предоставляют встроенную изоляцию данных и интегрированы с системой async/await.
  2. Для UI-обновлений применяйте @MainActor чтобы гарантировать работу с интерфейсом в главном потоке.
  3. В legacy-проектах или при работе с Objective-C совместимостью используйте DispatchQueue с барьерами.
  4. Для максимальной производительности в read-heavy сценариях рассмотрите DispatchQueue с concurrent чтением и барьерной записью.

Важные нюансы

// Даже это небезопасно - несколько await могут выполняться параллельно
var array: [Int] = []
for i in 1...100 {
    Task {
        array.append(i) // Разные Task могут работать в разных потоках
    }
}

// Правильно - дождаться завершения всех задач
func safeFill() async {
    var localArray: [Int] = []
    
    await withTaskGroup(of: Int.self) { group in
        for i in 1...100 {
            group.addTask { i }
        }
        
        for await value in group {
            localArray.append(value) // Все операции в одном контексте
        }
    }
}

Ключевой вывод: Сам по себе async/await не делает код потокобезопасным — он лишь предоставляет удобный синтаксис для асинхронных операций. Для безопасного наполнения массива в конкурентной среде необходимо использовать механизмы синхронизации: акторы, очереди, блокировки или изоляцию к определенному актору. Выбор конкретного механизма зависит от требований производительности, версии Swift и архитектуры приложения.