Sobes.tech
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.
    • NSLock
    • os_unfair_lock
    • pthread_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.main
    • DispatchQueue.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.
    • DispatchQueue com 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).