Sobes.tech
Middle+

Jak vyřešit problém závodní podmínky (race condition)?

sobes.tech AI

Odpověď od AI

Použití synchronizace vláken pro přístup ke sdíleným zdrojům.

Hlavní přístupy:

  • Mutexy (zámky): Zamezují přístup ke zdroji ostatním vláknům, dokud jedno vlákno zdroj používá.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Získání zámku
    // Kritická sekce - bezpečný přístup ke sharedResource
    sharedResource += 1
    print("Hodnota zdroje: \(sharedResource)")
    lock.unlock() // Uvolnění zámku
}

// Příklad volání z různých vláken
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Serial Queue (Sekvenční fronty): Provádějí úkoly jeden po druhém, zajišťují, že pouze jedno blok kódu má současně přístup ke zdroji.
    • DispatchQueue.main
    • DispatchQueue.global() s atributem .serial
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Kód se provádí sekvenčně
        sharedResource += 1
        print("Hodnota zdroje: \(sharedResource)")
    }
}

// Příklad volání z různých vláken/front
// modifyResourceAsync()
// modifyResourceAsync()
  • Čtenář-Writer Locks (zámky čtenář-písač): Umožňují více čtenářům číst současně, ale pouze jednomu písači zapisovat.
    • DispatchQueue s bariérami (.barrier) pro zápis a synchronní/asynchronní přístup pro čtení.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Čtení - více čtenářů současně
        print("Čtení zdroje: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Zápis - pouze jeden písač a nikdo jiný nečte/nepisuje
        sharedResource = newValue
        print("Zápis do zdroje: \(sharedResource)")
    }
}

// Příklad volání
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Atomické operace (Atomic Operations): Operace, které jsou garantovaně provedeny celé bez přerušení jinou vlákny. Používají se pro jednoduché datové typy (čítače, flagy).
    • OSAtomicIncrement, OSAtomicDecrement (zastaralé, ale koncept je stále relevantní)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

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

void incrementCounter() {
    atomicCounter++; // Atomické zvýšení
    std::cout << "Čítač: " << atomicCounter << std::endl;
}

// Příklad v C++ v kontextu iOS (použití s Objective-C++)
// Volat z různých vláken
  • Typy hodnot (Value Types): Přenos kopií struktur nebo enumerací místo odkazů na třídy může pomoci předejít problémům s race condition, pokud data nejsou sdílena přímo mezi vlákny v měnitelné podobě.

Volba metody závisí na specifikách úkolu a požadované úrovni paralelismu. Pro většinu úkolů v iOS jsou preferovány DispatchQueue (sekvenční a současné s bariérami) kvůli jednoduchosti použití a integraci s Grand Central Dispatch (GCD).