Sobes.tech
Middle+

Mit tudsz az olvasó és író problémájáról?

sobes.tech MI

Válasz az MI-től

Az olvasó és író problémája egy klasszikus feladat a közös adatok hozzáférésének szinkronizálására több szálas környezetben.

A probléma lényege:

  • Sok szál (olvasók) egyszerre olvassa az adatokat.
  • Sok szál (írók) egyszerre módosítja az adatokat.

Követelmények:

  • Az olvasók korlátozás nélkül olvashatnak egyszerre.
  • Az írók kizárólagos hozzáféréssel rendelkeznek az adatokhoz (amíg egy író dolgozik, senki más – sem olvasó, sem író – nem férhet hozzá).
  • Csak egy író dolgozhat egyszerre.
  • Ha egy író várakozik a hozzáférésre, új olvasóknak nem szabad hozzáférést kapniuk, amíg az író be nem fejezi a munkát. Ez a szabály megakadályozza az "éhező" írók problémáját.

Az iOS megoldásai:

  • NSLock: A legegyszerűbb mechanizmus, de nem optimális erre a feladatra, mivel blokkolja mind az olvasást, mind az írást.

  • NSRecursiveLock: Lehetővé teszi ugyanazon szál számára, hogy többször is megszerezze a zárolást. Nem alkalmazható.

  • NSCondition: Rugalmasabb mechanizmus, lehetővé teszi a szálak számára, hogy bizonyos feltételek teljesülését várják. Használható az olvasók/írók logikájának megvalósítására, de manuális zárolás- és feltételkezelést igényel.

  • Serial Dispatch Queue (GCD): Egy sorozatos sor létrehozása az összes olvasási és írási művelethez. Az írásokat szinkron módon hajtja végre, az olvasásokat aszinkron, de csak az előző műveletek befejezése után. Ez egyszerű megoldás, de nem a legjobb teljesítmény szempontjából az olvasásnál, mivel az olvasás nem lehet párhuzamos.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Adatok olvasási logika
            print("Adatok olvasása...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Adatok írási logika
            print("Adatok írása...")
        }
    }
    
  • Konkurrent Dispatch Queue Barrier-rel (GCD): A legtöbb esetben optimális megoldás. Konkurrent sor használatával párhuzamosan hajtódnak végre az olvasási műveletek. Az írási műveleteknél a .barrier zászlót alkalmazzuk. Amikor egy .barrier zászlóval ellátott művelet kerül a sorba, várja az összes korábbi művelet befejezését, majd végrehajtódik, és amíg végrehajtódik, más műveletek (olvasás vagy írás) nem történhetnek. A barrier művelet befejezése után a sor ismét konkurrent lesz. Így az olvasás párhuzamos, az írás pedig kizárólagos.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Adatok olvasási logika
            print("Adatok olvasása...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Adatok írási logika
            print("Adatok írása...")
        }
    }
    

    Ez ugyanaz a kód, mint a serial queue esetén, de a .concurrent attribútum a sort konkurrentté teszi. A .barrier zászló pedig azt eredményezi, hogy a sor csak erre az egy műveletre lesz szekvenciális.

  • os_unfair_lock (iOS 10+): Alacsony szintű zárolás, hatékonyabb, mint az NSLock. Használható összetettebb olvasó-író logika manuális megvalósítására, ha maximális teljesítmény vagy speciális viselkedés szükséges. Óvatos használatot igényel.

A megoldás kiválasztása az alkalmazás konkrét követelményeitől, az olvasási és írási műveletek gyakoriságától, valamint a szükséges teljesítményszinttől függ. A legtöbb esetben a .barrier zászlóval ellátott konkurrent dispatch queue a legkiegyensúlyozottabb és legajánlottabb megközelítés iOS-en.