Sobes.tech
Middle+

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

sobes.tech AI

Отговор от 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("Стойност на ресурса: \(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("Стойност на ресурса: \(sharedResource)")
    }
}

// Пример за извикване от различни потоци/опашки
// modifyResourceAsync()
// modifyResourceAsync()
  • Читател-писател (Reader-Writer) блокировки: Позволяват на множество потоци да четат едновременно, но само един да пише.
    • DispatchQueue с бариери (.barrier) за запис и синхронен/асинхронен достъп за четене.
import Foundation

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

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

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Запис - само един писател и никой не чете/писва
        sharedResource = newValue
        print("Писане на ресурс: \(sharedResource)")
    }
}

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

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

void incrementCounter() {
    atomicCounter++; // Атомарно увеличение
    std::cout << "Брояч: " << atomicCounter << std::endl;
}

// Пример за C++ в контекста на iOS (използване с Objective-C++)
// Извикване от различни потоци
  • Типове стойности: Могат да помогнат за избягване на проблеми с race condition, ако данните не се споделят директно между потоците в променяем вид.

Изборът на метод зависи от спецификата на задачата и нивото на паралелизъм, което е необходимо. За повечето задачи в iOS, DispatchQueue (сериен и паралелен с бариери) е предпочитан поради леснотата на използване и интеграцията с Grand Central Dispatch (GCD).