Ir para o conteúdo

Última atualização:2026-10-12

Depuração de Deadlocks em C++

Como investigar deadlocks em programação concorrente com C++?

Recentemente escrevi no Apache Arrow um operador com suporte a paralelismo: nested loop join. Como envolve paralelismo, aparece o famoso problema de deadlock. Se você se deparar com um deadlock, como depurar e localizá-lo?

Esse é o objetivo deste artigo: ajudá-lo a dominar rapidamente a detecção e a depuração de deadlocks em programação concorrente. Conteúdo direto ao ponto; vamos ao texto.

1.Introdução

Para explicar melhor o deadlock, introduzimos com um programa.

std::mutex gMutex;

int t2() {
  std::lock_guard<std::mutex> m(gMutex);
  return 0;
}

int t1() {
  std::lock_guard<std::mutex> l(gMutex);
  return t2();
}

Ao ver este programa, todos percebem o problema: deadlock! O problema é que tanto t1() quanto t2() adquirem o mutex global gMutex, mas t1(), após adquiri-lo, chama t2(), que por sua vez tenta adquirir gMutex novamente.

O lock de t1 já foi adquirido e ainda não foi liberado, e t2 tenta adquiri-lo novamente; ambos ficam esperando um pelo outro e entram em loop infinito. Em projetos reais o código não é tão simples quanto este — é muito complexo. Por exemplo, o código que escrevi no Apache Arrow é assim:

Status OnBuildSideFinished(size_t thread_index) {
  std::lock_guard<std::mutex> guard(probe_side_mutex_);
  // do something
  accumulate_build_ready_ = true;
  return scheduler_->StartTaskGroup(thread_index,task_group_probe_,queued_batches_to_probe_.batch_count()); 
}

Veja que este código é bem mais complexo que o cenário acima, com pelo menos 5 níveis de pilha aninhados — e ainda nem colei o restante. Mas, na essência, ambos seguem o mesmo modelo de deadlock.

2.Depuração

Apresentado o modelo de deadlock, como localizar o problema quando ele acontece?

Há duas abordagens. A primeira é executar o programa diretamente e depois anexar o gdb.

Por exemplo:

./a.out

Depois de encontrar o número do processo:

gdb -p xxx

Neste momento podemos obter as threads que estão esperando.

(gdb) info threads 
  Id   Target Id                                 Frame 
* 1    Thread 0x7ffff7fe2740 (LWP 32301) "a.out" 0x00007ffff7bc8017 in pthread_join () from /lib64/libpthread.so.0
  2    Thread 0x7ffff6fd0700 (LWP 32305) "a.out" 0x00007ffff7bcd54d in __lll_lock_wait () from /lib64/libpthread.so.0

Em seguida, olhe a pilha de __lll_lock_wait; aqui examinei a thread 2 e, ao ver a pilha, obtive os números de linha de t1 e t2, localizando diretamente onde está o problema — muito intuitivo!

Além dessa forma, também é possível iniciar o programa direto no gdb; ele travará, e após matar com ctrl + c você obtém o mesmo conteúdo do caso acima.

Por exemplo:

(gdb) r
Starting program: /home/light/a.out 
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Hello World!
[New Thread 0x7ffff6fd0700 (LWP 32305)]
^C
Thread 1 "a.out" received signal SIGINT, Interrupt.
0x00007ffff7bc8017 in pthread_join () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
(gdb) info threads 
  Id   Target Id                                 Frame 
* 1    Thread 0x7ffff7fe2740 (LWP 32301) "a.out" 0x00007ffff7bc8017 in pthread_join () from /lib64/libpthread.so.0
  2    Thread 0x7ffff6fd0700 (LWP 32305) "a.out" 0x00007ffff7bcd54d in __lll_lock_wait () from /lib64/libpthread.so.0

Bom, é isso para esta seção. Se gostou, compartilhe este artigo hardcore! Ao mesmo tempo, recomendo meu projeto de thread pool: expliquei o projeto em vídeo e incluí implantação com um clique via docker (pretendo gravar esse episódio em breve); se tiver interesse, é só me procurar.

Tópicos relacionados

Comentários