0Pricing
Swift Academy · Урок

Проблема гонки данных

Узнайте, почему одновременное изменение данных небезопасно.

«Проблема гонки данных» — бесплатный урок Swift Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Что такое гонка данных

Гонка данных возникает, когда два или более потока одновременно обращаются к одной области памяти, по крайней мере одна операция является записью, а синхронизация между ними отсутствует.

Результатом становится неопределённое поведение: повреждённые значения, сбои или ошибки, проявляющиеся только при высокой нагрузке.

Совместно используемое изменяемое состояние

Корень гонок данных — совместно используемое изменяемое состояние. Если многие задачи могут читать и изменять одну переменную, порядок операций становится непредсказуемым.

Приведённый ниже счётчик может терять приращения, потому что count += 1 — это чтение, изменение и запись, а не атомарная операция.

final class Counter {
    var count = 0
    func increment() {
        count += 1 // read, add, write: not atomic
    }
}

Почему приращения теряются

Инструкция count += 1 компилируется в три шага: загрузить значение, прибавить единицу и записать результат обратно.

Если два потока одновременно загрузят значение 5, оба запишут 6, и одно приращение исчезнет.

// Thread A loads 5
// Thread B loads 5
// Thread A stores 6
// Thread B stores 6  <-- lost update

Разрыв согласованности больших значений

Помимо потери обновлений, одновременная запись многословных значений, например структуры или 64-битного значения на некоторых платформах, может привести к разрыву: читатель увидит половину одной записи и половину другой.

struct Point { var x: Double; var y: Double }
var p = Point(x: 0, y: 0)
// Concurrent writes may leave x from one write and y from another

Старое решение: lock

До появления конкурентности Swift классическим решением был lock — мьютекс. Только один поток одновременно владеет lock, последовательно выполняя доступ к данным.

Lock работает, но его легко использовать неправильно: можно забыть вызвать unlock, создать взаимную блокировку или вызвать инверсию приоритетов.

import Foundation
final class SafeCounter {
    private let lock = NSLock()
    private var count = 0
    func increment() {
        lock.lock()
        defer { lock.unlock() }
        count += 1
    }
}

Последовательные очереди диспетчеризации

Другой классический подход — последовательная очередь диспетчеризации. Все изменения направляются в одну очередь, поэтому они никогда не выполняются одновременно.

import Foundation
final class QueueCounter {
    private let queue = DispatchQueue(label: "counter")
    private var count = 0
    func increment() {
        queue.async { self.count += 1 }
    }
}

Почему ручная синхронизация ненадёжна

Lock и очереди требуют дисциплины. Компилятор не проверяет, защищён ли каждый доступ.

Достаточно одной незащищённой операции чтения, чтобы гонка вернулась. Гарантии на этапе компиляции нет.

// Nothing stops a careless reader from doing this:
// let value = counter.count  // unsynchronized read = race

Конкурентность Swift меняет подход

Конкурентность Swift делает безопасность относительно гонок данных возможностью языка, а не соглашением между разработчиками.

Вместе работают три инструмента: actor для защиты изменяемого состояния, Sendable для безопасно передаваемых типов и компилятор, который обеспечивает соблюдение обоих правил.

actor Counter {
    private var count = 0
    func increment() { count += 1 }
}

Акторы последовательно предоставляют доступ

Актор гарантирует, что только одна задача одновременно выполняет его изменяющий состояние код. Среда выполнения автоматически упорядочивает доступ.

Вам не нужно писать lock: модель акторов обеспечивает синхронизацию.

actor BankAccount {
    private(set) var balance = 0
    func deposit(_ amount: Int) { balance += amount }
}

Проверка на этапе компиляции

Компилятор не позволяет обращаться к изолированному актором состоянию в обход актора. Вызовы между акторами становятся асинхронными (await).

Так ошибки времени выполнения, вызванные гонками, превращаются в ошибки времени компиляции.

let account = BankAccount()
// Must await: balance is actor-isolated
// let b = await account.balance

Передаваемый тип определяет, что пересекает границы потоков

Протокол Sendable обозначает типы, которые безопасно передавать через границы конкурентности.

Компилятор запрещает передавать изменяемое состояние, не соответствующее Sendable, в другую задачу, устраняя гонку на уровне типов.

struct Money: Sendable {
    let amount: Int
    let currency: String
}

Быстрая проверка: гонки данных

Проверьте, понимаете ли Вы, что вызывает гонку данных.

Повторение: проблема гонок данных

Гонки данных возникают, когда совместно используемое изменяемое состояние доступно одновременно без синхронизации, что приводит к потере обновлений, разрыву согласованности и неопределённому поведению.

Старые решения — lock и последовательные очереди — работают, но не проверяются компилятором и ненадёжны. Конкурентность Swift заменяет соглашения встроенным контролем: actor изолирует состояние, Sendable ограничивает данные, пересекающие границы, а компилятор проверяет безопасность. В оставшейся части курса эти инструменты рассматриваются подробно.

Часто задаваемые вопросы

Урок «Проблема гонки данных» бесплатный?

Да — полный текст урока «Проблема гонки данных» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «Проблема гонки данных»?

Узнайте, почему одновременное изменение данных небезопасно. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Проблема гонки данных»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Проблема гонки данных
  2. Протокол Sendable
  3. Изоляция акторов и nonisolated
  4. Переход на строгую конкурентность
← Назад к Swift Academy