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

Является ли массив потокобезопасным?

1.0 Junior🔥 201 комментариев
#Многопоточность и асинхронность

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

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

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

Являются ли стандартные Swift массивы потокобезопасными?

Нет, стандартные Swift массивы (Array<T>) НЕ являются потокобезопасными (thread-safe) по умолчанию. Они представляют собой изменяемые (mutable) структуры данных, доступ к которым из нескольких потоков одновременно без должной синхронизации может привести к неопределенному поведению (undefined behavior), данным гонок (data races), крашам (crashes) или повреждению данных (data corruption).

Почему массивы небезопасны в многопоточной среде?

Массив в Swift — это структура (value type), но это не делает её автоматически потокобезопасной. При одновременной модификации из разных потоков возникают следующие риски:

  1. Конкурирующие операции записи (Write-Write Race): Два потока пытаются одновременно изменить один и тот же элемент или структуру массива.
  2. Чтение во время записи (Read-Write Race): Один поток читает массив, в то время как другой его модифицирует.
  3. Внутренние нарушения целостности: Операции типа append, remove, insert изменяют внутренний буфер массива. Несинхронизированный доступ может привести к повреждению этого буфера.

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

import Foundation

var sharedArray = [Int]()
let queue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

// Поток 1: добавляет элементы
queue.async {
    for i in 0..<1000 {
        sharedArray.append(i)
    }
}

// Поток 2: также добавляет элементы
queue.async {
    for i in 1000..<2000 {
        sharedArray.append(i)
    }
}

// После выполнения возможны: краш, потеря данных, некорректный размер массива.

Как обеспечить потокобезопасность массивов?

Для безопасной работы с массивом из нескольких потоков необходимо использовать механизмы синхронизации:

1. Использование сериализующей очереди (Serial DispatchQueue)

class ThreadSafeArray<T> {
    private var array = [T]()
    private let queue = DispatchQueue(label: "com.example.threadSafeArray")
    
    var count: Int {
        return queue.sync { array.count }
    }
    
    func append(_ element: T) {
        queue.async(flags: .barrier) {
            self.array.append(element)
        }
    }
    
    func element(at index: Int) -> T? {
        return queue.sync {
            guard index < array.count else { return nil }
            return array[index]
        }
    }
}

2. Использование NSLock или os_unfair_lock

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

3. Actor (в Swift 5.5+)

actor SafeArray<T> {
    private var array = [T]()
    
    func append(_ element: T) {
        array.append(element)
    }
    
    func getElement(at index: Int) -> T? {
        guard index < array.count else { return nil }
        return array[index]
    }
}

// Использование:
Task {
    let safeArray = SafeArray<Int>()
    await safeArray.append(42)
    let value = await safeArray.getElement(at: 0)
}

Ключевые выводы:

  • Стандартные массивы Swift не потокобезопасны — они требуют внешней синхронизации.
  • Actor'ы — наиболее современный и рекомендованный способ обеспечения потокобезопасности, так как они гарантируют изоляцию состояния (state isolation) на уровне компилятора.
  • GCD (Grand Central Dispatch) с барьерами (barrier) или сериализующими очередями — классический подход, но требующий аккуратности.
  • Блокировки (locks) — низкоуровневый вариант, эффективный, но подверженный ошибкам (deadlocks, priority inversion).
  • Копирование массива (например, возврат копии при чтении) может быть временным решением, но неэффективно для больших данных.

Для простых сценариев использование DispatchQueue с барьерами (через .async(flags: .barrier)) или actor (в Swift Concurrency) является оптимальным выбором. Всегда анализируйте профиль доступа (чтение/запись) и выбирайте примитив синхронизации соответственно.