Sobes.tech
Middle+

Čo viete o probléme čitateľa a spisovateľa?

sobes.tech AI

Odpoveď od AI

Problém čitateľa a pisateľa je klasická úloha synchronizácie prístupu ku spoločným dátam v prostredí s viacerými vláknami.

Podstata problému:

  • Mnoho vlákien (čitateľov) súčasne číta dáta.
  • Mnoho vlákien (písateľov) súčasne mení dáta.

Požiadavky:

  • Čitatelia môžu čítať súčasne bez obmedzení.
  • Písatelia majú výhradný prístup k dátam (kým písateľ pracuje, nikto iný – ani čitateľ, ani písateľ – nemôže získať prístup).
  • Len jeden písateľ môže pracovať súčasne.
  • Ak písateľ čaká na prístup, noví čitatelia by nemali získať prístup, kým písateľ svoju prácu nedokončí. Toto pravidlo zabraňuje "hladovaniu" písateľov.

Riešenia v iOS:

  • NSLock: Najjednoduchší mechanizmus, ale nie je optimálny pre tento úkol, pretože blokuje ako čítanie, tak zápis.

  • NSRecursiveLock: Umožňuje rovnakému vláknu získať zámok viackrát. Nepoužíva sa.

  • NSCondition: Flexibilnejší mechanizmus, umožňuje vláknam čakať na splnenie určitých podmienok. Môže sa použiť na implementáciu logiky čitateľov/písateľov, ale vyžaduje ručné riadenie zámkov a podmienok.

  • Serial Dispatch Queue (GCD): Vytvorenie jednej sekvenčnej fronty pre všetky operácie čítania a zápisu. Zápisy sa vykonávajú synchronne, čítanie môže byť asynchrónne, ale iba po dokončení predchádzajúcich operácií. Ide o jednoduché riešenie, ale nie je optimálne z hľadiska výkonu pre čítanie, pretože čítanie nemôže byť paralelné.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logika čítania dát
            print("Čítanie dát...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logika písania dát
            print("Písanie dát...")
        }
    }
    
  • Konkurenčná dispatch queue s bariérou (GCD): Najlepšie riešenie vo väčšine prípadov. Používa konkurenčnú frontu pre paralelné vykonávanie operácií čítania. Pre operácie písania sa používa príznak .barrier. Keď je operácia s týmto príznakom vložená do fronty, čaká na dokončenie všetkých predchádzajúcich operácií, potom sa vykoná sama, a počas jej vykonávania sa nevykonávajú žiadne iné operácie (nielen čítanie, ale ani písanie). Po dokončení bariérovéj operácie sa fronta opäť stáva konkurenčnou. Takže čítanie je paralelné, písanie je exkluzívne.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logika čítania dát
            print("Čítanie dát...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logika písania dát
            print("Písanie dát...")
        }
    }
    

    Tento kód je rovnaký ako pri sériovej fronte, ale atribút .concurrent robí frontu konkurenčnou. Príznak .barrier spôsobí, že fronta sa správa ako sekvenčná len pre túto konkrétnu operáciu.

  • os_unfair_lock (iOS 10+): Nízkoúrovňové zámky, efektívnejšie než NSLock. Môže sa použiť na manuálnu implementáciu zložitejšej logiky čitateľov/písateľov, ak je požadovaná maximálna výkonnosť alebo špecifické správanie. Vyžaduje opatrné použitie.

Výber riešenia závisí od konkrétnych požiadaviek aplikácie, frekvencie operácií čítania a zápisu a požadovanej úrovne výkonu. Vo väčšine prípadov je najvyváženejšie a najodporúčanejšie použiť konkurenčnú frontu s príznakom .barrier v iOS.