Sobes.tech
Middle

Que métodos existem para combater colisões?

sobes.tech IA

Resposta da IA

Uma colisão, ou conflito, ocorre durante a fusão de alterações de diferentes ramos ou na interação de vários processos/threads que modificam os mesmos dados.

Os métodos para lidar com colisões dependem da sua natureza:

  1. Gestão de versões (por exemplo, Git):

    • Resolução manual: O Git marca as áreas conflitantes do código. O desenvolvedor edita manualmente os ficheiros, escolhendo as alterações necessárias.
    • Uso de ferramentas de fusão: Ferramentas visuais ajudam a comparar e fundir alterações linha por linha.
    • Rebase: Mover commits de um ramo para outro para criar uma história mais linear. Pode requerer resolver conflitos sequencialmente para cada commit.
    • Estratégias de fusão (Merge Strategies): O Git oferece várias estratégias (recursive, ours, theirs) para resolver automaticamente (ou semi-automaticamente) certos tipos de conflitos.
    • Fusões/Rebases frequentes: Reduzem a probabilidade de conflitos complexos.
    // Exemplo de código em conflito no Git que deve ser resolvido manualmente
    func updateData() {
        // Lógica de atualização de dados
    <<<<<<< HEAD
        let newValue = "Valor do BranchA"
    =======
        let newValue = "Valor do BranchB"
    >>>>>>> feature/branchB
        print("Atualizando com: \(newValue)")
    }
    
  2. Em multithreading/multiprocessamento (acesso concorrente a recursos):

    • Bloqueios (Locks/Mutexes): Permitem que apenas um thread aceda de forma exclusiva à secção crítica do código em qualquer momento.
      import Foundation
      
      let mutex = NSLock() // Criação de um mutex
      
      func performOperation() {
          mutex.lock() // Aquisição do bloqueio
          // Secção crítica: código que pode causar colisão em acesso paralelo
          print("A realizar operação segura para threads")
          mutex.unlock() // Liberação do bloqueio
      }
      
    • Semânticas de atomicidade: Operações que garantem serem executadas totalmente ou não serem executadas, sem interrupções de outros threads.
    • Uso de filas (Queues, por exemplo, GCD): Executar tarefas em filas sequenciais garante que o acesso a recursos comuns ocorre por um único thread.
      import Foundation
      
      let serialQueue = DispatchQueue(label: "com.example.mySerialQueue")
      
      func updateSharedData() {
          serialQueue.async {
              // Acesso a dados partilhados apenas a partir desta fila
              print("A atualizar dados partilhados de forma assíncrona")
          }
      }
      
    • Uso de filas concorrentes com barreiras (Concurrent Queues with Barriers): Permitem que múltiplos threads executem operações de leitura em paralelo, mas bloqueiam para operações de escrita.
      import Foundation
      
      let concurrentQueue = DispatchQueue(label: "com.example.myConcurrentQueue", attributes: .concurrent)
      var sharedArray = [Int]()
      
      func addValue(_ value: Int) {
          concurrentQueue.async(flags: .barrier) {
              // Operação de escrita - executada de forma exclusiva
              sharedArray.append(value)
              print("Adicionado \(value)")
          }
      }
      
      func readArray() {
          concurrentQueue.async {
              // Operação de leitura - pode ser executada em paralelo
              print("Array atual: \(sharedArray)")
          }
      }
      
    • Operações atômicas: Operações de baixo nível que garantem atomicidade (por exemplo, usando funções OSAtomic* ou atomics de C++11).
    • Memória transacional (Software Transactional Memory - STM): Abordagem de alto nível que permite agrupar uma série de operações numa transação que deve ter sucesso na sua totalidade ou ser revertida completamente.

A escolha do método depende do contexto e do tipo de colisão.