Sobes.tech
Middle+

How to solve a race condition problem?

sobes.tech AI

Answer from AI

Use thread synchronization for access to shared resources.

Main approaches:

  • Mutexes: Block access to a resource for other threads while one thread is using it.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Acquire mutex
    // Critical section - safe access to sharedResource
    sharedResource += 1
    print("Resource value: \(sharedResource)")
    lock.unlock() // Release mutex
}

// Example calls from different threads
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Serial Queues: Execute tasks one after another, ensuring only one block of code accesses the resource at a time.
    • DispatchQueue.main
    • DispatchQueue.global() with .serial attribute
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Code executes sequentially within this queue
        sharedResource += 1
        print("Resource value: \(sharedResource)")
    }
}

// Example calls from different threads/queues
// modifyResourceAsync()
// modifyResourceAsync()
  • Reader-Writer Locks: Allow multiple threads to read a resource simultaneously, but only one thread to write.
    • DispatchQueue with barriers (.barrier) for writing and synchronous/asynchronous access for reading.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Reading - multiple readers at once
        print("Reading resource: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Writing - only one writer, no readers/writers
        sharedResource = newValue
        print("Writing resource: \(sharedResource)")
    }
}

// Example calls
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Atomic Operations: Operations guaranteed to execute atomically without interruption by other threads. Used for simple data types (counters, flags).
    • OSAtomicIncrement, OSAtomicDecrement (Deprecated but still relevant)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

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

void incrementCounter() {
    atomicCounter++; // Atomic increment
    std::cout << "Counter: " << atomicCounter << std::endl;
}
// Example in C++ in the context of iOS (using Objective-C++)
// Call from different threads
  • Value Types: Passing copies of structs/enums instead of class references can help avoid data races if data is not shared directly between threads in mutable form.

The choice of method depends on the specifics of the task and the required level of parallelism. For most iOS tasks, DispatchQueue (Serial and Concurrent with barriers) is preferred for ease of use and integration with Grand Central Dispatch (GCD).