Sobes.tech
Middle+

O que sabes sobre o problema do leitor e do escritor?

sobes.tech IA

Resposta da IA

O problema do leitor e do escritor é uma tarefa clássica de sincronização de acesso a dados partilhados em ambientes multithread.

A essência do problema:

  • Múltiplos threads (leitores) leem dados simultaneamente.
  • Múltiplos threads (escritores) modificam dados simultaneamente.

Requisitos:

  • Os leitores podem ler sem restrições simultaneamente.
  • Os escritores têm acesso exclusivo aos dados (enquanto um escritor trabalha, ninguém mais, nem leitor nem escritor, pode aceder).
  • Apenas um escritor pode trabalhar de cada vez.
  • Se um escritor espera acesso, os novos leitores não devem aceder até que o escritor termine. Esta regra evita a "fome" dos escritores.

Soluções em iOS:

  • NSLock: Mecanismo simples, mas não ótimo para esta tarefa, pois bloqueia tanto leitura como escrita.

  • NSRecursiveLock: Permite que o mesmo thread obtenha o bloqueio várias vezes. Não aplicável.

  • NSCondition: Mecanismo mais flexível que permite aos threads esperar certas condições. Pode ser usado para implementar lógica de leitores/escritores, mas requer gestão manual de bloqueios e condições.

  • Serial Dispatch Queue (GCD): Criação de uma fila sequencial para todas as operações de leitura e escrita. As escritas são feitas de forma síncrona, a leitura pode ser feita de forma assíncrona, mas apenas após as operações anteriores terminarem. É uma solução simples, mas não ótima em desempenho para leitura, pois a leitura não pode ser paralela.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Lógica de leitura de dados
            print("Lendo dados...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Lógica de escrita de dados
            print("Escrevendo dados...")
        }
    }
    
  • Concurrent Dispatch Queue com Barrier (GCD): Solução ótima na maioria dos casos. Usa uma fila concorrente para executar operações de leitura em paralelo. Para operações de escrita, usa a bandeira .barrier. Quando uma operação com essa bandeira é colocada na fila, ela espera que todas as operações anteriores terminem, depois é executada, e enquanto ela está a ser executada, nenhuma outra operação (leitura ou escrita) pode ocorrer. Após a sua conclusão, a fila volta a ser concorrente. Assim, a leitura é paralela, e a escrita é exclusiva.

    let readWriteQueue = DispatchQueue(label: "com.example.readwrite", attributes: .concurrent)
    
    func readData() {
        readWriteQueue.async {
            // Lógica de leitura de dados
            print("Lendo dados...")
        }
    }
    
    func writeData() {
        readWriteQueue.sync(flags: .barrier) {
            // Lógica de escrita de dados
            print("Escrevendo dados...")
        }
    }
    

    É o mesmo código que para a fila serial, mas o atributo .concurrent faz a fila ser concorrente. A bandeira .barrier faz a fila comportar-se como sequencial apenas para essa operação específica.

  • os_unfair_lock (iOS 10+): Bloqueio de baixo nível, mais eficiente que NSLock. Pode ser usado para implementar lógica mais complexa de leitores/escritores manualmente, se for necessário máximo desempenho ou comportamento específico. Requer uso cuidadoso.

A escolha da solução depende dos requisitos específicos da aplicação, da frequência de operações de leitura e escrita, e do nível de desempenho necessário. Na maioria dos casos, usar uma fila de despacho concorrente com a bandeira .barrier é a abordagem mais equilibrada e recomendada em iOS.