Назад к вопросам
Middle+
136
questionbank

Как решить проблему гонки данных (race condition)?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Использовать синхронизацию потоков доступа к общим ресурсам.

Основные подходы:

  • Mutexes (Мьютексы): Блокируют доступ к ресурсу для других потоков, пока один поток его использует.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Захватываем мьютекс
    // Критическая секция - безопасный доступ к sharedResource
    sharedResource += 1
    print("Resource value: \(sharedResource)")
    lock.unlock() // Освобождаем мьютекс
}

// Пример вызова из разных потоков
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Serial Queues (Последовательные очереди): Исполняют задачи одну за другой, гарантируя, что одновременно только один блок кода имеет доступ к ресурсу.
    • DispatchQueue.main
    • DispatchQueue.global() с атрибутом .serial
import Foundation

let serialQueue = DispatchQueue(label: "com.example.serialQueue")
var sharedResource = 0

func modifyResourceAsync() {
    serialQueue.async {
        // Код выполняется последовательно в рамках этой очереди
        sharedResource += 1
        print("Resource value: \(sharedResource)")
    }
}

// Пример вызова из разных потоков/очередей
// modifyResourceAsync()
// modifyResourceAsync()
  • Reader-Writer Locks (Блокировка читатель-писатель): Позволяют множеству потоков читать ресурс одновременно, но только одному потоку писать в него.
    • DispatchQueue с барьерами (.barrier) для записи и синхронным/асинхронным доступом для чтения.
import Foundation

let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
var sharedResource = 0

func readResource() {
    concurrentQueue.async { // Чтение - множество читателей одновременно
        print("Reading resource: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Запись - только один писатель и никто не читает/пишет
        sharedResource = newValue
        print("Writing resource: \(sharedResource)")
    }
}

// Пример вызовов
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Atomic Operations (Атомарные операции): Операции, которые гарантированно выполняются целиком без прерывания другим потоком. Используются для простых типов данных (счетчики, флаги).
    • OSAtomicIncrement, OSAtomicDecrement (Устаревшие, но концепция актуальна)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

std::atomic<int> atomicCounter(0);

void incrementCounter() {
    atomicCounter++; // Атомарное инкрементирование
    std::cout << "Counter: " << atomicCounter << std::endl;
}

// Пример C++ в контексте iOS (использование с Objective-C++)
// Вызывать из разных потоков
  • Value Types (Типы значений): Передача копий структур/перечислений вместо ссылок на классы может помочь избежать проблем с гонкой данных, если данные не разделяются напрямую между потоками в изменяемом виде.

Выбор метода зависит от специфики задачи и требуемого уровня параллелизма. Для большинства задач в iOS предпочтительны DispatchQueue (Serial и Concurrent с барьерами) из-за легкости использования и интеграции с Grand Central Dispatch (GCD).