Multithreading e Concorrência no Desenvolvimento Móvel: O Que É, Princípios e Como Funciona

Autor: IT Sectr Publicado: 2026-03-12 Tempo de leitura: 13 min

Toda aplicação móvel executa múltiplas tarefas simultaneamente: carrega dados da rede, processa toques do utilizador, anima a interface e guarda ficheiros. Se todo este código roda numa única thread, a aplicação congela com qualquer atraso de rede. Multithreading e concorrência são conceitos-chave que permitem que a aplicação permaneça responsiva e eficiente. Neste artigo abordaremos todas as ferramentas principais: desde Main Thread e RunLoop até corrotinas Kotlin e Combine no iOS. O material baseia-se na documentação oficial da Apple GCD.

Principais Conclusões

  • Main Thread — a única thread para trabalhar com a UI; todas as outras tarefas vão para Background
  • GCD e OperationQueue — os principais mecanismos de multithreading no iOS
  • Coroutines e Flow — o padrão moderno de assincronia em Kotlin/Android
  • RxJava, RxSwift e Combine — frameworks reativos para trabalhar com fluxos de dados
  • Race Condition, Deadlock e Livelock — problemas clássicos de multithreading que exigem sincronização
  • A escolha da ferramenta depende da plataforma e complexidade da tarefa: para chamadas simples basta Async/Await, para fluxos complexos — Rx ou Combine

O que é Multithreading?

Multithreading é a capacidade de uma aplicação executar vários fragmentos de código simultaneamente. Cada fragmento executa numa thread separada — um processo leve com sua própria pilha de chamadas. No desenvolvimento móvel, as threads dividem-se em duas categorias: Main Thread (thread da UI) e Background Threads (threads de fundo).

O sistema operativo gere a distribuição das threads pelos núcleos do processador. Os dispositivos modernos têm 6–8 núcleos, portanto a execução paralela pode realmente acelerar o trabalho. No entanto, criar threads é uma operação cara, por isso não é recomendado trabalhar diretamente com Thread. Em vez disso, usam-se abstrações de alto nível: DispatchQueue, OperationQueue, CoroutineDispatcher.

Concorrência é um conceito mais amplo que multithreading. Concorrência significa que as tarefas podem ser executadas "simultaneamente" mesmo num único núcleo através de mudança de contexto. Assincronia (Async/Await) é um modelo de programação onde uma tarefa não bloqueia uma thread, mas devolve o controlo enquanto aguarda um resultado. As linguagens modernas (Kotlin, Swift, Dart) têm suporte nativo a Async/Await.

Na IT Sectr, damos especial atenção à arquitetura correta de multithreading no início do projeto. Erros cometidos numa fase inicial levam a bugs difíceis de detetar: condições de corrida, bloqueios mútuos e instabilidade da aplicação sob carga. Cada um dos nossos projetos passa por uma revisão de arquitetura de concorrência na fase de planeamento.

Threads Principais (Main/Background)

Main Thread (thread principal) — a única thread numa aplicação móvel que tem acesso à UI. No Android chama-se UI Thread, no iOS — Main Thread. Todas as operações com a interface — alterar texto, animações, processar toques — são realizadas apenas na Main Thread. Se uma operação pesada (carregamento de ficheiro, análise JSON) for executada na thread principal, a interface deixa de responder. No Android isto leva a ANR (Application Not Responding), no iOS — a um ecrã "congelado".

Background Threads (threads de fundo) destinam-se a tudo o que não está relacionado com a UI: pedidos de rede, operações de base de dados, processamento de imagens, criptografia. Após a conclusão, o resultado é passado para a Main Thread para exibição. Cada plataforma fornece as suas próprias ferramentas para alternar entre threads: DispatchQueue.main.async no iOS, runOnUiThread ou withContext(Dispatchers.Main) no Android.

RunLoop — o ciclo de processamento de eventos na thread principal do iOS. RunLoop aguarda eventos (toques, temporizadores, notificações) e despacha-os para os manipuladores apropriados. No Android o equivalente é Looper, associado a cada Main Thread. Main Looper extrai infinitamente mensagens da fila e passa-as ao Handler para processamento. Compreender RunLoop e Looper ajuda a evitar fugas de memória e "gaguejo" da interface.

GCD e OperationQueue (iOS)

Grand Central Dispatch (GCD) — biblioteca da Apple para gestão de multithreading ao nível da linguagem C. GCD trabalha com DispatchQueue — filas de tarefas. O programador não cria threads manualmente; o GCD gere um conjunto de threads (Thread Pool), distribuindo tarefas pelos núcleos disponíveis do processador. DispatchQueue são de dois tipos: Serial Queue (fila serial — as tarefas executam-se uma após a outra) e Concurrent Queue (fila concorrente — as tarefas podem executar-se simultaneamente).

Main DispatchQueue — uma fila serial ligada à thread principal. Global Queues — filas concorrentes com diferentes prioridades (QoS — Quality of Service): userInteractive, userInitiated, utility, background. Escolher o QoS correto é crítico para o desempenho: .userInteractive — para tarefas que afetam a UI (animações, renderização); .background — para tarefas não críticas em termos de tempo (sincronização, limpeza de cache).

OperationQueue — uma camada superior sobre o GCD com capacidades adicionais: cancelamento de tarefas, definição de dependências entre operações, controlo do número máximo de operações concorrentes. As operações são objetos da classe Operation (ou BlockOperation). Exemplo: se precisar de carregar uma imagem, depois aplicar um filtro e só então mostrá-la — OperationQueue com dependências lida perfeitamente com isso. No GCD teria de sincronizar manualmente esses passos usando DispatchGroup ou semáforo.

Async/Await no Swift 5.5+ — uma alternativa moderna ao GCD. As palavras-chave async e await tornam o código assíncrono linear e legível. As funções são marcadas como async e as chamadas são aguardadas com await. O sistema gere automaticamente a mudança de contexto: por defeito, uma função async executa numa thread de fundo, enquanto a atualização da UI executa no MainActor. @MainActor — um atributo que garante a execução do código na thread principal.

Coroutines e Flow (Kotlin)

Coroutines (corrotinas) — são threads leves para Kotlin desenvolvidas pela JetBrains. Ao contrário das threads normais, as corrotinas não estão ligadas a uma Thread específica. Milhares de corrotinas podem executar em várias threads sem sobrecarga significativa. CoroutineScope gere o ciclo de vida das corrotinas: viewModelScope está ligado ao ViewModel, lifecycleScope — à Activity/Fragment. Quando o âmbito é destruído, todas as corrotinas filhas são automaticamente canceladas.

Dispatchers determinam em que conjunto de threads a corrotina executa: Dispatchers.Main — thread da UI; Dispatchers.IO — para pedidos de rede e operações de disco; Dispatchers.Default — para cálculos intensivos de CPU. Para mudar de dispatcher usa-se withContext. As corrotinas suportam concorrência estruturada: cada corrotina tem um pai, e quando o pai é cancelado, todas as corrotinas filhas são canceladas. Isto evita fugas de memória e tarefas penduradas.

Flow — um fluxo de dados assíncrono frio da biblioteca de corrotinas. Flow emite valores sequencialmente: (1) o produtor gera dados, (2) os operadores transformam o fluxo, (3) o coletor consome o resultado. Ao contrário do LiveData, o Flow suporta cadeias complexas de operadores (map, filter, flatMapConcat, catch) e é completamente thread-safe. StateFlow e SharedFlow — variantes quentes do Flow, ideais para estado da UI e eventos únicos (Snackbar, navegação).

Channel — outra abstração de corrotinas para passar dados entre corrotinas. Channel funciona como uma fila: um remetente (send) e um ou mais recetores (receive). Canais com buffer (Channel(UNLIMITED), Channel(BUFFERED)) permitem configurar o comportamento em caso de transbordo. Channel é frequentemente usado com Flow para fazer ponte entre APIs baseadas em callbacks e corrotinas: callbackFlow { … }.

Na IT Sectr usamos ativamente corrotinas e Flow em todos os projetos Android. Isto permite escrever código assíncrono que parece síncrono, é fácil de testar (runTest, TestDispatcher) e não requer gestão manual de threads. Exemplo de uma corrotina simples com carregamento de dados:

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
        return@withContext try {
            val users = api.fetchUsers()
            dao.insertAll(users)
            users
        } catch (e: Exception) {
            dao.getAll()
        }
    }
}

Rx e Combine

Programação reativa — um paradigma onde os dados se propagam como fluxos assíncronos (Observable, Publisher). RxJava/RxKotlin — a implementação mais popular para Android, portada do .NET Rx. RxSwift — biblioteca semelhante para iOS. Componentes principais: Observable (fonte de eventos), Observer (subscritor), Scheduler (gestão de threads), Operators (transformação de fluxo).

Combine — framework da Apple para programação reativa apresentado no iOS 13. Combine usa os protocolos Publisher (publicador) e Subscriber (subscritor). Ao contrário do RxSwift, o Combine está integrado no SDK e intimamente integrado com SwiftUI. Operadores no Combine: map, filter, combineLatest, zip, debounce, throttle — cobrem a maioria dos cenários: desde ligação de dados à UI até debounce de consulta de pesquisa.

Future e Promise — padrões para trabalhar com um único resultado assíncrono. Future representa um valor que estará disponível mais tarde. Promise é uma promessa de fornecer um valor. No Rx isto é Single (uma resposta bem-sucedida ou erro), no Combine — Future Publisher. Na prática, Future/Promise são convenientes para pedidos únicos a API, enquanto Observable/Publisher — para fluxos contínuos (geolocalização, entrada de texto).

Callback e Delegate — padrões clássicos para operações assíncronas. Callback — uma função passada como argumento e chamada quando a operação termina. Delegate — um objeto que implementa um protocolo com métodos manipuladores de eventos. Desvantagem: "callback hell" (callbacks aninhados) e complexidade de tratamento de erros. NotificationCenter (iOS) e EventBus (Android) — mecanismos de transmissão de eventos, úteis para comunicação fracamente acoplada, mas que levam a dependências implícitas.

Problemas de Multithreading (Race Condition, Deadlock)

Multithreading abre a porta a alto desempenho, mas simultaneamente cria o risco de erros difíceis de detetar. Os mais comuns: Race Condition (condição de corrida), Deadlock (bloqueio mútuo), Livelock (bloqueio ativo) e Starvation (inanição de thread). Compreender estes problemas é uma habilidade essencial para qualquer programador móvel.

Race Condition

Race Condition ocorre quando duas ou mais threads leem e escrevem os mesmos dados simultaneamente sem sincronização. O resultado depende de qual thread executa primeiro. Exemplo clássico: duas threads incrementam um contador. A operação "ler → incrementar → escrever" não é atómica, portanto quando executada simultaneamente, um incremento "perde-se". A solução — usar operações atómicas (AtomicInteger, AtomicReference) ou bloqueios (Mutex, Semaphore, synchronized).

Deadlock

Deadlock — situação onde cada thread detém um recurso e aguarda um recurso detido por outra thread. Nenhuma thread pode prosseguir. Condições de ocorrência: exclusão mútua, retenção e espera, sem preempção, espera circular. Prevenção: estabelecer uma ordem única de aquisição de bloqueios, usar tryLock com tempo limite, aplicar algoritmos Lock-Free (ConcurrentHashMap, CopyOnWriteArrayList).

Livelock e Starvation

Livelock — as threads não estão bloqueadas mas passam constantemente recursos umas às outras sem realizar trabalho útil. Exemplo: duas pessoas encontram-se num corredor e ambas se desviam, movendo-se na mesma direção. Starvation — uma thread não obtém acesso a um recurso porque outras threads o intercetam constantemente. Solução: bloqueios justos (fair locks), prioridades de threads com cautela.

Ferramentas de Sincronização

Para prevenir problemas de multithreading usam-se primitivas de sincronização: Mutex (exclusão mútua), Semaphore (limitação do número de acessos simultâneos), Lock (interface com tryLock), Synchronized (bloqueio ao nível da JVM), @MainActor (Swift — garantia de execução na thread principal). No Android também está disponível ThreadPool (conjunto de threads) através de Executors.newFixedThreadPool, newCachedThreadPool. No entanto, a gestão manual de conjuntos é prerrogativa de projetos legados; em novos projetos é melhor usar corrotinas.

Ferramenta Plataforma Tipo Características
DispatchQueue (GCD)iOSFila de tarefasSerial/Concurrent, prioridades QoS, Thread Pool gerido pelo sistema
OperationQueueiOSFila de operaçõesDependências, cancelamento, maxConcurrentOperationCount
Coroutines + FlowAndroidCorrotinasLeves, concorrência estruturada, StateFlow, Channel
RxJava / RxKotlinAndroidFluxo reativoObservable, Schedulers, conjunto rico de operadores
CombineiOSFluxo reativoPublisher/Subscriber, integração com SwiftUI
Async/Await + TaskiOS / AndroidModelo assíncronoCódigo linear, @MainActor, concorrência estruturada

Perguntas Frequentes

Qual a diferença entre Main Thread e Background Thread?

Main Thread (thread da UI) é responsável por renderizar a interface e processar toques. Background Thread executa tarefas de fundo — carregamento de dados, cálculos, trabalho de rede. Bloquear a Main Thread causa congelamento da interface (ANR no Android, frozen UI no iOS).

O que é Race Condition e como evitá-la?

Race Condition — condição de corrida quando duas threads acedem simultaneamente a dados partilhados e o resultado depende da ordem de execução. Evita-se através de sincronização: Mutex, Semaphore, Lock, Synchronized, @MainActor ou operações atómicas.

Coroutines ou RxJava: qual escolher para Android?

Coroutines é o padrão moderno para Android (JetBrains, suportado pelo Google). RxJava/RxKotlin é uma abordagem reativa com um rico conjunto de operadores. Coroutines é mais simples para chamadas assíncronas, RxJava é mais poderoso para fluxos de dados complexos. Na IT Sectr usamos Coroutines + Flow para novos projetos.

O que são Deadlock e Livelock?

Deadlock — bloqueio mútuo onde duas threads esperam pelos recursos uma da outra. Livelock — as threads não estão bloqueadas mas passam recursos constantemente sem fazer trabalho útil. Ambos os problemas resolvem-se com ordem adequada de bloqueios e tempos limite.

Porque é necessário DispatchQueue no iOS?

DispatchQueue é uma abstração do Grand Central Dispatch (GCD) para gestão de threads. Main Queue executa tarefas na thread principal, Global Queues — em threads de fundo. Serial Queue garante execução sequencial, Concurrent Queue — paralela. Em projetos modernos, o GCD é frequentemente substituído por Async/Await e Task.

Resumo

  • Main Thread — apenas UI; todas as outras operações vão para Background
  • GCD e OperationQueue — a base do multithreading no iOS; Async/Await — a alternativa moderna
  • Coroutines e Flow — o padrão para Android; concorrência estruturada previne fugas
  • RxJava, RxSwift, Combine — frameworks reativos para fluxos de dados complexos
  • Race Condition e Deadlock — os principais problemas; resolvem-se com bloqueios e ordem adequada de aquisição de recursos
  • Thread Pool é gerido pelo sistema (GCD) ou framework (corrotinas); a criação manual de threads não é recomendada
  • A escolha da ferramenta depende da plataforma: Coroutines para Android, GCD/Combine para iOS

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto