Sobes.tech
Middle+

Ce știi despre problema cititorului și a scriitorului?

sobes.tech AI

Răspuns de la AI

Problema cititorului și scriitorului este o sarcină clasică de sincronizare a accesului la datele comune într-un mediu multithread.

Esenta problemei:

  • Multiple fire (cititori) citesc datele simultan.
  • Multiple fire (scriitori) modifică datele simultan.

Cerinte:

  • Cititorii pot citi simultan fără restricții.
  • Scriitorii au acces exclusiv la date (atunci când un scriitor lucrează, nimeni altcineva, nici cititor, nici scriitor, nu poate avea acces).
  • Doar un singur scriitor poate lucra simultan.
  • Dacă un scriitor așteaptă accesul, noii cititori nu trebuie să îl obțină până când scriitorul nu își termină munca. Această regulă previne "foametea" scriitorilor.

Soluții în iOS:

  • NSLock: Mecanism simplu, dar nu optim pentru această sarcină, deoarece blochează atât citirea, cât și scrierea.

  • NSRecursiveLock: Permite aceluiași fir să obțină blocajul de mai multe ori. Nu se aplică.

  • NSCondition: Mecanism mai flexibil, care permite firelor să aștepte anumite condiții. Poate fi folosit pentru a implementa logica cititorilor/scriitorilor, dar necesită gestionare manuală a blocajelor și condițiilor.

  • Serial Dispatch Queue (GCD): Crearea unei cozi secvențiale pentru toate operațiile de citire și scriere. Scrierile se realizează sincron, citirile pot fi asincrone, dar doar după finalizarea operațiilor anterioare. Este o soluție simplă, dar nu optimă din punct de vedere al performanței pentru citire, deoarece citirea nu poate fi paralelă.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logica citirii datelor
            print("Citire date...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logica scrierii datelor
            print("Scriere date...")
        }
    }
    
  • Coada de dispatch concurentă cu barrier (GCD): Soluția optimă în cele mai multe cazuri. Se folosește o coadă concurentă pentru a executa operațiile de citire în paralel. Pentru operațiile de scriere, se folosește flag-ul .barrier. Când o operație cu acest flag este plasată în coadă, ea așteaptă finalizarea tuturor operațiilor anterioare, apoi se execută, și în timpul execuției, alte operații (citire sau scriere) nu pot avea loc. După finalizare, coada devine din nou concurentă. Astfel, citirea este paralelă, iar scrierea este exclusivă.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Logica citirii datelor
            print("Citire date...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Logica scrierii datelor
            print("Scriere date...")
        }
    }
    

    Același cod ca pentru coada secvențială, dar atributul .concurrent face ca coada să fie concurentă. Flag-ul .barrier face ca coada să se comporte ca secvențială doar pentru această operație.

  • os_unfair_lock (iOS 10+): Blocare de nivel inferior, mai eficientă decât NSLock. Poate fi folosită pentru a implementa manual logica mai complexă a cititorilor/scriitorilor, dacă este necesară performanță maximă sau comportament specific. Necesită utilizare atentă.

Alegerea soluției depinde de cerințele specifice ale aplicației, de frecvența operațiilor de citire și scriere, și de nivelul de performanță necesar. În cele mai multe cazuri, utilizarea unei cozi de dispatch concurente cu flag-ul .barrier reprezintă abordarea cea mai echilibrată și recomandată în iOS.