В чем разница между Data Race и Race Condition в многопоточности?

«В чем разница между Data Race и Race Condition в многопоточности?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Data Race (Гонка данных) — это конкретный, низкоуровневый сценарий, когда два или более потока одновременно обращаются к одной ячейке памяти, и хотя бы один доступ — это запись, без должной синхронизации. Это приводит к неопределенному поведению (undefined behavior) и может вызвать сбои, коррупцию данных или краши.

Race Condition (Состояние гонки) — это более широкое, логическое понятие, когда корректность работы программы зависит от относительного порядка или времени выполнения операций в параллельных потоках. Результат становится непредсказуемым и может быть логически некорректным, даже если технического одновременного доступа к памяти не происходит.

Ключевое отличие: Data Race — это подмножество Race Condition, сфокусированное именно на несинхронизированном доступе к памяти.

Пример Data Race (опасный одновременный доступ):

var counter = 0 // Разделяемая mutable-переменная

DispatchQueue.concurrentPerform(iterations: 1000) { _ in
    // МНОГО ПОТОКОВ читают и пишут `counter` без синхронизации
    counter += 1 // <- DATA RACE! Операция не атомарна (read-modify-write).
}
// Итоговое значение `counter` может быть меньше 1000.

Решение: Использовать синхронизацию (например, DispatchQueue с барьером, NSLock, os_unfair_lock или атомарные операции).

Пример Race Condition без Data Race (логическая ошибка):

var cachedValue: Int?
var valueIsCalculated = false // Флаг-синхронизация предотвращает Data Race

// Поток A
if !valueIsCalculated {
    cachedValue = expensiveCalculation() // (1)
    valueIsCalculated = true             // (2)
}

// Поток B (выполняется параллельно)
if !valueIsCalculated {
    cachedValue = expensiveCalculation() // (3) Может выполниться ПОСЛЕ (1), но ДО (2)!
    valueIsCalculated = true
}
// RACE CONDITION: Дорогой расчет может быть выполнен ДВАЖДЫ,
// хотя технической гонки данных за флагом и значением нет.

Решение: Использовать примитивы для однократного выполнения (например, dispatch_once в Objective-C) или более строгую синхронизацию всего блока кода.

Как избежать проблем:

  1. Для предотвращения Data Race: Используйте синхронизацию (serial DispatchQueue, locks, actors в Swift) или immutable данные.
  2. Для предотвращения Race Condition: Проектируйте потокобезопасные интерфейсы, где операции либо атомарны, либо не зависят от порядка выполнения.