Sobes.tech
Middle+

Wat weet je over het probleem van de lezer en de schrijver?

sobes.tech AI

Antwoord van AI

Het probleem van lezer en schrijver is een klassiek synchronisatietaak voor toegang tot gedeelde gegevens in een multithread-omgeving.

De kern van het probleem:

  • Meerdere threads (lezers) lezen gelijktijdig gegevens.
  • Meerdere threads (schrijvers) wijzigen gelijktijdig gegevens.

Vereisten:

  • Lezers kunnen gelijktijdig lezen zonder beperkingen.
  • Schrijvers hebben exclusieve toegang tot de gegevens (zolang een schrijver werkt, kan niemand anders, noch lezer noch schrijver, toegang krijgen).
  • Er mag slechts één schrijver tegelijk werken.
  • Als een schrijver wacht op toegang, mogen nieuwe lezers dat niet krijgen totdat de schrijver klaar is. Deze regel voorkomt "honger" bij schrijvers.

Oplossingen in iOS:

  • NSLock: Eenvoudig mechanisme, maar niet optimaal voor deze taak, omdat het zowel lezen als schrijven blokkeert.

  • NSRecursiveLock: Maakt het mogelijk dat dezelfde thread de lock meerdere keren verkrijgt. Niet toepasbaar.

  • NSCondition: Flexibeler mechanisme dat threads laat wachten op bepaalde voorwaarden. Kan worden gebruikt om de lezer/schrijver-logica te implementeren, maar vereist handmatig beheer van locks en voorwaarden.

  • Serial Dispatch Queue (GCD): Creëer een sequentiële wachtrij voor alle lees- en schrijfoperaties. Schrijfbewerkingen worden synchroon uitgevoerd, lezen kan asynchroon, maar alleen nadat eerdere operaties zijn voltooid. Dit is een eenvoudige oplossing, maar niet optimaal qua prestaties voor lezen, omdat lezen niet parallel kan.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logica voor het lezen van gegevens
            print("Gegevens lezen...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logica voor het schrijven van gegevens
            print("Gegevens schrijven...")
        }
    }
    
  • Concurrent Dispatch Queue met Barrier (GCD): De meest optimale oplossing in de meeste gevallen. Gebruikt een gelijktijdige wachtrij om leesoperaties parallel uit te voeren. Voor schrijfoperaties wordt de .barrier-vlag gebruikt. Wanneer een operatie met deze vlag in de wachtrij wordt geplaatst, wacht deze tot alle voorgaande operaties zijn voltooid, wordt dan uitgevoerd, en tijdens de uitvoering kunnen geen andere operaties (lezen of schrijven) plaatsvinden. Na voltooiing wordt de wachtrij weer concurrent.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logica voor het lezen van gegevens
            print("Gegevens lezen...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logica voor het schrijven van gegevens
            print("Gegevens schrijven...")
        }
    }
    

    Hetzelfde code als voor de sequentiële wachtrij, maar de .concurrent-attribuut maakt de wachtrij gelijktijdig. De .barrier-vlag zorgt ervoor dat de wachtrij zich gedraagt als sequentieel alleen voor deze specifieke operatie.

  • os_unfair_lock (iOS 10+): Laag-niveau lock, efficiënter dan NSLock. Kan worden gebruikt om handmatig complexere lezer/schrijver-logica te implementeren, indien maximale prestaties of specifiek gedrag vereist is. Voorzichtig gebruik vereist.

De keuze van de oplossing hangt af van de specifieke eisen van de applicatie, de frequentie van lees- en schrijfoperaties, en het benodigde prestatieniveau. In de meeste gevallen is het gebruik van een gelijktijdige dispatch queue met de .barrier-vlag de meest gebalanceerde en aanbevolen aanpak in iOS.