Является ли Task в Swift атомарной операцией?

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

Ответ

Нет, Task в Swift не является атомарной операцией по умолчанию. Task — это единица асинхронной работы, которая может выполняться конкурентно. Операции внутри одного Task или между разными Task не синхронизированы автоматически.

Проблема: При одновременном доступе к изменяемому общему состоянию (shared mutable state) из разных Task возникает состояние гонки (data race), ведущее к неопределенному поведению и сбоям.

Пример состояния гонки:

var counter = 0 // Разделяемое изменяемое состояние

Task {
    for _ in 1...1000 {
        counter += 1 // Неатомарная операция: чтение-изменение-запись
    }
}

Task {
    for _ in 1...1000 {
        counter += 1 // Параллельное выполнение приводит к потере обновлений
    }
}
// Итоговое значение counter, скорее всего, будет меньше 2000.

Решение: использование механизмов синхронизации.

1. Акторы (Actors) — предпочтительный способ в Swift Concurrency:

actor CounterActor {
    private var value = 0

    func increment() {
        value += 1 // Доступ к `value` извне изолирован
    }

    func getValue() -> Int {
        return value
    }
}

// Использование:
let counter = CounterActor()
Task {
    await counter.increment() // Вызовы сериализуются
}

2. Примитивы из Dispatch (для интеграции с legacy-кодом):

import Dispatch

class ThreadSafeCounter {
    private var lock = NSLock()
    private var value = 0

    func increment() {
        lock.lock()
        defer { lock.unlock() } // Гарантирует разблокировку
        value += 1
    }
}

Вывод: Для обеспечения атомарности и потокобезопасности при работе с общими данными в Task необходимо явно использовать акторы, семафоры (DispatchSemaphore), очереди (DispatchQueue с барьерами) или другие примитивы синхронизации.