Sobes.tech
Middle+

Πώς λύνεται το πρόβλημα του ανταγωνισμού (race condition);

sobes.tech AI

Απάντηση από AI

Χρήση συγχρονισμού ροών πρόσβασης σε κοινόχρηστους πόρους.

Βασικές προσεγγίσεις:

  • Mutexes (Μαζί): Κλειδώνουν την πρόσβαση σε πόρο για άλλες ροές, ενώ μια ροή τον χρησιμοποιεί.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Αποκτάμε το mutex
    // Κρίσιμη ενότητα - ασφαλής πρόσβαση στο sharedResource
    sharedResource += 1
    print("Τιμή πόρου: \(sharedResource)")
    lock.unlock() // Απελευθερώνουμε το mutex
}

// Παράδειγμα κλήσης από διαφορετικές ροές
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Ακολουθίες (Serial Queues): Εκτελούν εργασίες μία-μία, διασφαλίζοντας ότι μόνο ένα μπλοκ κώδικα έχει πρόσβαση σε πόρο ταυτόχρονα.
    • DispatchQueue.main
    • DispatchQueue.global() με χαρακτηριστικό .serial
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Ο κώδικας εκτελείται διαδοχικά
        sharedResource += 1
        print("Τιμή πόρου: \(sharedResource)")
    }
}

// Παράδειγμα κλήσης από διαφορετικές ροές/ουρές
// modifyResourceAsync()
// modifyResourceAsync()
  • Αναγνώστης-Γράφτης Κλειδώματα (Reader-Writer Locks): Επιτρέπουν σε πολλές ροές να διαβάζουν ταυτόχρονα, αλλά μόνο μία να γράφει.
    • DispatchQueue με barriers (.barrier) για εγγραφή και συγχρονισμένη/ασύγχρονη πρόσβαση για ανάγνωση.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Ανάγνωση - πολλοί αναγνώστες ταυτόχρονα
        print("Ανάγνωση πόρου: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Εγγραφή - μόνο ένας συγγραφέας και κανείς άλλος δεν διαβάζει/γράφει
        sharedResource = newValue
        print("Γράφοντας πόρο: \(sharedResource)")
    }
}

// Παράδειγμα κλήσεων
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Ατομικές Ενέργειες (Atomic Operations): Ενέργειες που εκτελούνται πλήρως και χωρίς διακοπή από άλλη ροή. Χρησιμοποιούνται για απλούς τύπους δεδομένων (μετρητές, σημαίες).
    • OSAtomicIncrement, OSAtomicDecrement (Παλαιότερα, αλλά η ιδέα παραμένει актуальна)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

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

void incrementCounter() {
    atomicCounter++; // Ατομική αύξηση
    std::cout << "Μετρητής: " << atomicCounter << std::endl;
}

// Παράδειγμα C++ στο πλαίσιο του iOS (χρήση με Objective-C++)
// Κάλεσε από διαφορετικές ροές
  • Τύποι Αξιών (Value Types): Η μεταφορά αντιγράφων δομών/περιγραφών αντί για αναφορές σε κλάσεις μπορεί να βοηθήσει στην αποφυγή προβλημάτων με τον ανταγωνισμό δεδομένων, αν τα δεδομένα δεν μοιράζονται άμεσα μεταξύ των ροών σε μεταβλητή μορφή.

Η επιλογή της μεθόδου εξαρτάται από τη φύση της εργασίας και το απαιτούμενο επίπεδο παράλληλης εκτέλεσης. Για τις περισσότερες εργασίες στο iOS, προτιμώνται DispatchQueue (Serial και Concurrent με barriers) λόγω ευκολίας χρήσης και ενσωμάτωσης με το Grand Central Dispatch (GCD).