Middle+
Como resolver o problema de condição de corrida (race condition)?
sobes.tech IA
Resposta da IA
Utilizar a sincronização de threads para aceder a recursos partilhados.
Abordagens principais:
- Mutexes (Mutual Exclusion): Bloqueiam o acesso a um recurso para outros threads enquanto um o está a usar.
NSLockos_unfair_lockpthread_mutex_t
import Foundation
let lock = NSLock()
var sharedResource = 0
func modifyResource() {
lock.lock() // Adquirir o mutex
// Secção crítica - acesso seguro a sharedResource
sharedResource += 1
print("Valor do recurso: \(sharedResource)")
lock.unlock() // Libertar o mutex
}
// Exemplo de chamada de diferentes threads
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
- Filas seriais: Executam tarefas uma após a outra, garantindo que apenas um bloco de código tem acesso ao recurso de cada vez.
DispatchQueue.mainDispatchQueue.global()com atributo.serial
import Foundation
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
var sharedResource = 0
func modifyResourceAsync() {
serialQueue.async {
// Código executa-se sequencialmente nesta fila
sharedResource += 1
print("Valor do recurso: \(sharedResource)")
}
}
// Exemplo de chamadas de diferentes threads/filas
// modifyResourceAsync()
// modifyResourceAsync()
- Bloqueio leitor-escritor: Permitem que múltiplos threads leiam o recurso ao mesmo tempo, mas apenas um pode escrever.
DispatchQueuecom barreiras (.barrier) para escrita e acesso síncrono/assíncrono para leitura.
import Foundation
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)
var sharedResource = 0
func readResource() {
concurrentQueue.async { // Leitura - múltiplos leitores ao mesmo tempo
print("A ler recurso: \(sharedResource)")
}
}
func writeResource(newValue: Int) {
concurrentQueue.sync(flags: .barrier) { // Escrita - apenas um escritor e ninguém lê/escreve
sharedResource = newValue
print("A escrever recurso: \(sharedResource)")
}
}
// Exemplo de chamadas
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
- Operações atómicas: Operações que garantem execução completa sem interrupção por outros threads. São usadas para tipos de dados simples (contadores, flags).
OSAtomicIncrement,OSAtomicDecrement(Obsoletos, mas a ideia mantém-se)<atomic>em C++11
#include <atomic>
#include <iostream>
std::atomic<int> atomicCounter(0);
void incrementCounter() {
atomicCounter++; // Incremento atómico
std::cout << "Contador: " << atomicCounter << std::endl;
}
// Exemplo em contexto iOS com C++ (uso com Objective-C++)
// Chamar de diferentes threads
- Tipos de valor: Passar cópias de estruturas/enums em vez de referências a classes pode ajudar a evitar problemas de condições de corrida, se os dados não forem partilhados diretamente entre threads de forma modificável.
A escolha do método depende da natureza da tarefa e do nível de paralelismo necessário. Para a maioria das tarefas em iOS, DispatchQueue (Serial e Concorrente com barreiras) é preferido devido à facilidade de uso e integração com Grand Central Dispatch (GCD).