Middle+
Jak rozwiązać problem wyścigu (race condition)?
sobes.tech AI
Odpowiedź od AI
Używanie synchronizacji wątków do dostępu do zasobów współdzielonych.
Główne podejścia:
- Mutexy (Mutual Exclusion): Blokują dostęp do zasobu dla innych wątków, gdy jeden z nich go używa.
NSLockos_unfair_lockpthread_mutex_t
import Foundation
let lock = NSLock()
var sharedResource = 0
func modifyResource() {
lock.lock() // Zdobycie mutexa
// Sekcja krytyczna - bezpieczny dostęp do sharedResource
sharedResource += 1
print("Wartość zasobu: \(sharedResource)")
lock.unlock() // Zwolnienie mutexa
}
// Przykład wywołania z różnych wątków
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
- Kolejki seryjne: Wykonują zadania jedno po drugim, gwarantując, że tylko jeden blok kodu ma dostęp do zasobu w danym czasie.
DispatchQueue.mainDispatchQueue.global()z atrybutem.serial
import Foundation
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
var sharedResource = 0
func modifyResourceAsync() {
serialQueue.async {
// Kod wykonywany kolejno w tej kolejce
sharedResource += 1
print("Wartość zasobu: \(sharedResource)")
}
}
// Przykład wywołań z różnych wątków/kolejek
// modifyResourceAsync()
// modifyResourceAsync()
- Blokady czytelnik-pisarz: Pozwalają wielu wątkom czytać zasób jednocześnie, ale tylko jednemu pisać.
DispatchQueuez barierami (.barrier) dla zapisu i synchronicznym/asynchronicznym dostępem do odczytu.
import Foundation
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
var sharedResource = 0
func readResource() {
concurrentQueue.async { // Odczyt - wielu czytelników jednocześnie
print("Odczyt zasobu: \(sharedResource)")
}
}
func writeResource(newValue: Int) {
concurrentQueue.sync(flags: .barrier) { // Zapis - tylko jeden pisarz i nikt nie czyta/nie pisze
sharedResource = newValue
print("Zapis zasobu: \(sharedResource)")
}
}
// Przykład wywołań
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
- Operacje atomowe: Operacje gwarantujące wykonanie w całości bez przerwania przez inny wątek. Używane dla prostych typów danych (liczniki, flagi).
OSAtomicIncrement,OSAtomicDecrement(Przestarzałe, ale koncepcja nadal aktualna)<atomic>w C++11
#include <atomic>
#include <iostream>
std::atomic<int> atomicCounter(0);
void incrementCounter() {
atomicCounter++; // Atomowe zwiększenie
std::cout << "Licznik: " << atomicCounter << std::endl;
}
// Przykład w kontekście iOS z C++ (użycie z Objective-C++)
// Wywołać z różnych wątków
- Typy wartości: Przekazywanie kopii struktur/enumeracji zamiast referencji do klas może pomóc uniknąć problemów z wyścigiem danych, jeśli dane nie są bezpośrednio współdzielone między wątkami w zmiennym stanie.
Wybór metody zależy od specyfiki zadania i wymaganego poziomu równoległości. Dla większości zadań w iOS preferowane są DispatchQueue (seryjne i równoległe z barierami) ze względu na łatwość użycia i integrację z Grand Central Dispatch (GCD).