Проблема гонки данных
Узнайте, почему одновременное изменение данных небезопасно.
«Проблема гонки данных» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Проблема гонки данных
- Протокол Sendable
- Изоляция акторов и nonisolated
- Переход на строгую конкурентность