Sobes.tech
Middle+

Wie löst man das Problem des Race Conditions?

sobes.tech KI

Antwort von AI

Verwenden Sie die Thread-Synchronisation, um auf gemeinsame Ressourcen zuzugreifen.

Hauptansätze:

  • Mutexe (Mutual Exclusion): Sperren den Zugriff auf eine Ressource für andere Threads, während ein Thread sie verwendet.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Mutex sperren
    // Kritischer Abschnitt - sicherer Zugriff auf sharedResource
    sharedResource += 1
    print("Ressourcenwert: \(sharedResource)")
    lock.unlock() // Mutex freigeben
}

// Beispiel für Aufrufe aus verschiedenen Threads
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Serielle Warteschlangen: Führen Aufgaben nacheinander aus, garantieren, dass nur ein Codeblock gleichzeitig Zugriff auf die Ressource hat.
    • DispatchQueue.main
    • DispatchQueue.global() mit Attribut .serial
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Code wird sequenziell in dieser Warteschlange ausgeführt
        sharedResource += 1
        print("Ressourcenwert: \(sharedResource)")
    }
}

// Beispiel für Aufrufe aus verschiedenen Threads/Warteschlangen
// modifyResourceAsync()
// modifyResourceAsync()
  • Leser-Schreiber-Sperren: Erlauben mehreren Threads, gleichzeitig zu lesen, aber nur einem zu schreiben.
    • DispatchQueue mit Barrieren (.barrier) für Schreibzugriffe und synchronem/asynchronem Lesezugriff.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Lesen - mehrere Leser gleichzeitig
        print("Lesen der Ressource: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Schreiben - nur ein Schreiber, keine Leser oder andere Schreiber
        sharedResource = newValue
        print("Schreiben der Ressource: \(sharedResource)")
    }
}

// Beispiel für Aufrufe
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Atomare Operationen: Garantierte vollständige Ausführung ohne Unterbrechung durch andere Threads. Für einfache Datentypen (Zähler, Flags) verwendet.
    • OSAtomicIncrement, OSAtomicDecrement (veraltet, aber das Konzept ist relevant)
    • <atomic> in C++11
#include <atomic>
#include <iostream>

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

void incrementCounter() {
    atomicCounter++; // Atomarer Inkrement
    std::cout << "Zähler: " << atomicCounter << std::endl;
}

// Beispiel im iOS-Kontext mit C++ (Verwendung mit Objective-C++)
// Aus verschiedenen Threads aufrufen
  • Werttypen: Das Übergeben von Kopien von Strukturen/Enums anstelle von Referenzen auf Klassen kann helfen, Race-Conditions zu vermeiden, wenn die Daten nicht direkt zwischen Threads in änderbarer Form geteilt werden.

Die Wahl der Methode hängt von der Art der Aufgabe und dem erforderlichen Parallelitätsgrad ab. Für die meisten Aufgaben in iOS sind DispatchQueue (seriell und parallel mit Barrieren) aufgrund der einfachen Handhabung und Integration mit Grand Central Dispatch (GCD) vorzuziehen.