Являются ли структуры (struct) в Swift потокобезопасными по умолчанию?

Ответ

Нет. Несмотря на то, что структуры (value-типы) копируются при присваивании или передаче в функцию, они не являются автоматически потокобезопасными при работе с изменяемым (mutating) экземпляром, доступным из нескольких потоков.

Проблема возникает, когда:

  • У вас есть изменяемая (var) переменная структуры.
  • Несколько потоков одновременно вызывают её mutating методы или изменяют свойства.
  • Это приводит к гонке данных, так как операция += не атомарна.

Пример гонки:

struct Counter {
    var value = 0
    mutating func increment() { value += 1 }
}

var counter = Counter() // Изменяемый экземпляр
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter.increment() // ❌ Гонка данных! Каждый поток читает и пишет в одну область памяти.
}
// Итоговое значение `counter.value` будет случайным.

Как обеспечить потокобезопасность для структуры:

  1. Инкапсулировать в потокобезопасный класс (см. предыдущий ответ).
  2. Использовать мьютекс или очередь внутри структуры (редко, нарушает семантику value-типа).
  3. Использовать актор (actor) для управления изменяемым состоянием.
  4. Спроектировать систему так, чтобы каждый поток работал со своей копией структуры, а результаты объединялись через синхронизированный механизм.

Ключевая мысль: Потокобезопасность зависит не от типа (класс/структура), а от того, как организован доступ к изменяемому состоянию. Value-типы лишь упрощают создание изолированных копий.

Ответ 18+ 🔞

А, слушай, вот эта хуйня про структуры и потоки — это вообще отдельная песня, блядь. Люди думают: «О, структура, value-тип, копируется — значит, потокобезопасно, ёпта!». А потом сидят с рандомными багами и чешут репу, как мартышка под хвостом.

Так вот, блядь, нет. Нихуя подобного.

В чём, сука, подвох? Да, когда ты передаёшь структуру — она копируется, это факт. Но если у тебя есть одна изменяемая переменная этой структуры, и с неё одновременно начинают дрочить несколько потоков, вызывая mutating методы — это пиздец, Карл! Гонка данных, ёбаный в рот! Операция += — это же не атомарная магия, это «прочитал — изменил — записал». И если два потока прочитали одно и то же значение, а потом оба его «увеличили» и записали обратно — один инкремент нахуй сгорает в аду. Итоговое число будет как повезёт, как будто его хуем по таблице Менделеева тыкали.

Смотри, какой пиздец может выйти:

struct Counter {
    var value = 0
    mutating func increment() { value += 1 }
}

var counter = Counter() // Вот она, наша общая изменяемая хуйня
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    counter.increment() // ❌ Иди нахуй, гонка данных! Все лезут в одну кучу.
}
// В конце counter.value будет какой-то левый пиздёж, а не 1000.

Так как же, блядь, сделать чтобы не было стыдно?

  1. Запихни эту структуру в потокобезопасный класс, как я в прошлый раз говорил. Класс будет через мьютекс или очередь доступ синхронизировать.
  2. Можно, конечно, и в саму структуру мьютекс воткнуть, но это уже извращение, блядь. Структура тогда теряет всю свою value-семантику, превращается в какую-то мудя с ушами.
  3. Используй актор (actor). Это сейчас модно и правильно. Он сам заботится об изоляции своего состояния.
  4. Самое элегантное — спроектировать так, чтобы каждый поток работал со своей собственной, ебейшей, изолированной копией структуры. А потом, через какой-нибудь синхронизированный механизм, результаты собрать. Вот тогда да, ценность value-типов раскрывается.

Короче, запомни раз и нахуй: Потокобезопасность — это не про «структура или класс». Это про то, как ты организуешь доступ к изменяемому состоянию. Структуры лишь дают удобный инструмент, чтобы делать изолированные копии и не париться. Но если ты этими копиями не пользуешься, а долбишься всеми потоками в одну переменную — пиши пропало, чувак. Жди сюрпризов.