Sobes.tech
Middle+

Co víte o problému čtenáře a spisovatele?

sobes.tech AI

Odpověď od AI

Problém čtenáře a pisatele je klasickým úkolem synchronizace přístupu ke sdíleným datům v prostředí s více vlákny.

Podstata problému:

  • Mnoho vláken (čtenáři) současně čte data.
  • Mnoho vláken (pisatelé) současně upravuje data.

Požadavky:

  • Čtenáři mohou číst současně bez omezení.
  • Pisatelé mají výhradní přístup k datům (dokud pisatel pracuje, nikdo jiný – ani čtenář, ani pisatel – nemůže získat přístup).
  • Současně může pracovat pouze jeden pisatel.
  • Pokud pisatel čeká na přístup, noví čtenáři by neměli získat přístup, dokud pisatel svou práci nedokončí. Toto pravidlo zabraňuje "hladovění" pisatelů.

Řešení v iOS:

  • NSLock: Nejjednodušší mechanismus, ale není optimální pro tento úkol, protože blokuje jak čtení, tak zápis.

  • NSRecursiveLock: Umožňuje stejnému vláknu získat zámek několikrát. Nepoužívá se.

  • NSCondition: Flexibilnější mechanismus, umožňuje vláknům čekat na splnění určitých podmínek. Lze jej použít k implementaci logiky čtenářů/pisatelů, ale vyžaduje ruční správu zámků a podmínek.

  • Serial Dispatch Queue (GCD): Vytvoření jedné sekvenční fronty pro všechny operace čtení a zápisu. Zápisy se provádějí synchronně, čtení může být asynchronní, ale pouze po dokončení předchozích operací. Jedná se o jednoduché řešení, ale není optimální z hlediska výkonu pro čtení, protože čtení nemůže být paralelní.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logika čtení dat
            print("Čtení dat...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logika zápisu dat
            print("Zápis dat...")
        }
    }
    
  • Konkurenční dispatch queue s bariérou (GCD): Nejlepší řešení ve většině případů. Používá konkurenční frontu pro paralelní provádění operací čtení. Pro zápisy se používá příznak .barrier. Když je operace s tímto příznakem vložena do fronty, čeká na dokončení všech předchozích operací, poté se provede sama a během jejího provádění se nevykonávají žádné jiné operace (čtení ani zápis). Po dokončení bariérové operace se fronta opět stává konkurenční. Tím je čtení paralelní, zápis je exkluzivní.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logika čtení dat
            print("Čtení dat...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logika zápisu dat
            print("Zápis dat...")
        }
    }
    

    Tento kód je stejný jako u sériové fronty, ale atribut .concurrent činí frontu konkurenční. Příznak .barrier způsobí, že fronta se chová jako sekvenční pouze pro tuto konkrétní operaci.

  • os_unfair_lock (iOS 10+): Nízké úrovně zámku, výkonnější než NSLock. Lze jej použít k ruční implementaci složitější logiky čtenářů/pisatelů, pokud je požadována maximální výkonnost nebo specifické chování. Vyžaduje opatrné použití.

Výběr řešení závisí na konkrétních požadavcích aplikace, frekvenci operací čtení a zápisu a požadované úrovni výkonu. Ve většině případů je nejvyváženější a doporučený přístup použití konkurenční fronty s příznakem .barrier v iOS.