Sobes.tech
Middle+

Ako vyriešiť problém závodného stavu (race condition)?

sobes.tech AI

Odpoveď od AI

Použitie synchronizácie vlákien na prístup k spoločným zdrojom.

Hlavné prístupy:

  • Mutexy (zámky): Blokujú prístup ku zdroju pre iné vlákna, kým jedno vlákno zdroj používa.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Získanie muteksa
    // Kritická sekcia - bezpečný prístup k sharedResource
    sharedResource += 1
    print("Hodnota zdroja: \(sharedResource)")
    lock.unlock() // Uvoľnenie muteksa
}

// Príklad volania z rôznych vlákien
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Sériové fronty (Serial Queues): Vykonávajú úlohy jedna po druhej, zabezpečujúc, že iba jeden blok kódu má súčasne prístup ku zdroju.
    • DispatchQueue.main
    • DispatchQueue.global() s atribútom .serial
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Kód sa vykonáva sekvenčne
        sharedResource += 1
        print("Hodnota zdroja: \(sharedResource)")
    }
}

// Príklad volania z rôznych vlákien/front
// modifyResourceAsync()
// modifyResourceAsync()
  • Čitateľ-Writer Locks (zámky čitateľ-písač): Umožňujú viacerým vláknam čítať súčasne, ale len jednému písať.
    • DispatchQueue s bariérami (.barrier) pre zápis a synchronizovaným/asynchrónnym prístupom pre čítanie.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Čítanie - viacerí čitatelia súčasne
        print("Čítanie zdroja: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Zápis - len jeden pisateľ a nikto iný nečíta/nepíše
        sharedResource = newValue
        print("Písanie do zdroja: \(sharedResource)")
    }
}

// Príklad volania
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Atomické operácie: Operácie, ktoré sú garantovane vykonané celé bez prerušenia iným vláknom. Používajú sa pre jednoduché typy dát (čítače, flagy).
    • OSAtomicIncrement, OSAtomicDecrement (zastaralé, ale koncept je stále aktuálny)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

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

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

// Príklad C++ v kontexte iOS (použitie s Objective-C++)
// Volajte z rôznych vlákien
  • Typy hodnôt (Value Types): Prenos kópií štruktúr alebo enumerácií môže pomôcť vyhnúť sa problémom s race condition, ak sa údaje nezdielajú priamo medzi vláknami v meniteľnej podobe.

Výber metódy závisí od špecifík úlohy a požadovanej úrovne paralelizmu. Pre väčšinu úloh v iOS sú vhodné DispatchQueue (sériové a paralelné s bariérami) kvôli ľahkosti použitia a integrácii s Grand Central Dispatch (GCD).