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

Что может произойти, если писать одновременно из нескольких потоков в массив?

2.0 Middle🔥 221 комментариев
#Многопоточность и асинхронность#Язык Swift

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

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

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

Проблемы при многопоточном изменении массива

Если несколько потоков одновременно пытаются модифицировать массив (например, добавлять или удалять элементы) без должной синхронизации, это приводит к серьезным проблемам в работе программы. Основные риски:

1. Разрушение инвариантов структуры данных

Массивы в Swift (и большинстве языков) имеют внутреннюю структуру, которая должна оставаться корректной при операциях. При одновременных изменениях эта структура нарушается.

// Пример опасного кода
var sharedArray = [Int]()

DispatchQueue.concurrentPerform(iterations: 100) { i in
    sharedArray.append(i) // Множество потоков пишет одновременно
}

2. Непредсказуемые результаты и гонки данных (Data Race)

  • Потеря данных: Одни операции добавления могут быть "перезаписаны" другими.
  • Неверный порядок элементов: Элементы появляются в массиве в случайном порядке.
  • Несоответствие размеров: array.count может возвращать неправильное значение.

3. Полное падение программы (Crash)

В худшем случае конкуренция приводит к фатальным ошибкам памяти:

  • Двойное освобождение памяти: Если два потока одновременно удаляют элементы.
  • Обращение к поврежденным указателям: Внутренние указатели массива становятся некорректными.
  • Неконтролируемое исключение: В Swift это может вызвать EXC_BAD_ACCESS или аналогичные.
// Типичные симптомы при тестировании
Thread 1: удаляет элемент по index
Thread 2: одновременно читает тот же индекс -> Crash

Почему это происходит технически?

Массив — не thread-safe структура. Его внутренние операции:

  1. Вычисление нового размера при добавлении
  2. Перераспределение памяти при необходимости
  3. Копирование элементов при расширении
  4. Обновление счетчика элементов (count)

Все эти шаги требуют последовательного выполнения. Если поток B начинает изменять массив, когда поток A еще не завершил свою модификацию, состояние становится неопределенным.

Решения для безопасной многопоточности

Использование механизмов синхронизации

DispatchQueue с барьером для чтения/записи:

class SafeArray<T> {
    private var array = [T]()
    private let queue = DispatchQueue(label: "safe.array", attributes: .concurrent)
    
    func append(_ element: T) {
        queue.async(flags: .barrier) {
            self.array.append(element)
        }
    }
    
    var count: Int {
        queue.sync {
            return array.count
        }
    }
}

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

var array = [Int]()
let lock = NSLock()

DispatchQueue.global().async {
    lock.lock()
    array.append(1)
    lock.unlock()
}

Использование Actor (в Swift 5.5+)

actor ActorArray {
    private var storage = [Int]()
    
    func append(_ value: Int) {
        storage.append(value)
    }
    
    var count: Int {
        storage.count
    }
}

// Использование
let actorArray = ActorArray()
Task {
    await actorArray.append(10)
}

Ключевые рекомендации

  • Всегда синхронизируйте операции изменения массива между потоками.
  • Рассмотрите альтернативы: Используйте раздельные массивы для каждого потока или каналы передачи данных.
  • Профилируйте код: Инструменты типа Thread Sanitizer в Xcode помогают обнаружить гонки данных.
  • Избегайте блокировок в высоконагруженных системах: Используйте конкурентные структуры из DispatchQueue или Actor модель.

Итог: Прямое многопоточное изменение массива без защиты — классическая ошибка, приводящая к нестабильности программы. Правильная синхронизация обязательна для надежной многопоточной работы.

Что может произойти, если писать одновременно из нескольких потоков в массив? | PrepBro