Sobes.tech
Middle+

Kuidas lahendada võistlusolukorra (race condition) probleem?

sobes.tech AI

Vastus AI-lt

Kasuta niidide sünkroneerimist ühiste ressursside juurdepääsu jaoks.

Peamised lähenemisviisid:

  • Mutexid (Muteksid): Blokeerivad juurdepääsu ressursile teistele niitidele, kuni üks niit kasutab seda.
    • NSLock
    • os_unfair_lock
    • pthread_mutex_t
import Foundation

let lock = NSLock()
var sharedResource = 0

func modifyResource() {
    lock.lock() // Muteksi lukustamine
    // Kriitiline sektsioon - turvaline juurdepääs sharedResource'ile
    sharedResource += 1
    print("Resursside väärtus: \(sharedResource)")
    lock.unlock() // Muteksi vabastamine
}

// Näide erinevatest niitidest
// DispatchQueue.global().async { modifyResource() }
// DispatchQueue.global().async { modifyResource() }
  • Süntesaatorid (Serial Queues): Täidavad ülesandeid ükshaaval, tagades, et korraga on ainult üks koodiplokk juurdepääs ressursile.
    • DispatchQueue.main
    • DispatchQueue.global() koos .serial atribuudiga
import Foundation

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

func modifyResourceAsync() {
    serialQueue.async {
        // Kood täidetakse järjekorras
        sharedResource += 1
        print("Resursside väärtus: \(sharedResource)")
    }
}

// Näide erinevatest niitidest/järjestustest
// modifyResourceAsync()
// modifyResourceAsync()
  • Lugejate-Kirjutajate lukud (Reader-Writer Locks): võimaldavad paljudel lugijatel samaaegselt lugeda ressurssi, kuid ainult ühe kirjutaja.
    • DispatchQueue barjääridega (.barrier) kirjutamiseks ja sünkroneerimata/asinkroneerimata lugemiseks.
import Foundation

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

func readResource() {
    concurrentQueue.async { // Lugemine - paljud lugejad samaaegselt
        print("Lugedes ressurssi: \(sharedResource)")
    }
}

func writeResource(newValue: Int) {
    concurrentQueue.sync(flags: .barrier) { // Kirjutamine - ainult üks kirjutaja ja keegi ei loe ega kirjuta
        sharedResource = newValue
        print("Kirjutades ressurssi: \(sharedResource)")
    }
}

// Näide
// readResource()
// readResource()
// writeResource(newValue: 10)
// readResource()
  • Atomaarsed operatsioonid: operatsioonid, mis täidetakse garanteeritult kogu aeg ilma teiste niitide katkestusteta. Kasutatakse lihtsate andmetüüpide (loendurid, lipud) puhul.
    • OSAtomicIncrement, OSAtomicDecrement (vananenud, kuid kontseptsioon kehtib)
    • C++11 <atomic>
#include <atomic>
#include <iostream>

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

void incrementCounter() {
    atomicCounter++; // Atomaarselt suurendamine
    std::cout << "Loendur: " << atomicCounter << std::endl;
}

// Näide C++-s iOS kontekstis (kasutades Objective-C++)
// Kutsub erinevatest niitidest
  • Väärtusetüübid (Value Types): Struktuuride või enumide koopiate edastamine, mitte viidete kasutamine klassidele, võib aidata vältida andmekonkurentsi probleeme, kui andmed ei jagata otse muutmise eesmärgil niitide vahel.

Valik sõltub ülesande spetsiifikast ja nõutavast paralleelismitasemest. Enamasti iOS-is on soovitatav kasutada DispatchQueue (Serial ja Concurrent barjääridega) lihtsuse ja GCD-ga integreerimise tõttu.