Última atualização:2026-10-12
Depuração All-Stop multi-thread com GDB¶
Quando usamos o gdb para depurar programas multi-thread, os threads podem executar a qualquer momento; quando tentamos depurar um thread específico (por exemplo: next), isso pode causar a troca para outro thread, o que não corresponde à nossa expectativa de avançar para a próxima linha no thread atual.
Thread 2 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5ba840, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399 size_t thread_index = thread_indexer_();
(gdb) n
[Switching to Thread 0x7ffff2c08700 (LWP 29121)]
Thread 3 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399 size_t thread_index = thread_indexer_();
A resposta é o recurso que vamos introduzir hoje: o bloqueio do escalonador (scheduler-locking). O GDB não consegue executar todos os threads em lockstep. Como o escalonamento de threads depende do sistema operacional do seu alvo de depuração (e não é controlado pelo GDB), quando o thread atual conclui uma operação de passo único, outros threads podem ter executado várias instruções. Além disso, em geral, quando o programa para, os outros threads param no meio de uma instrução, e não em um limite claro de instrução. Chega a acontecer de, depois de continuar ou executar um passo, você encontrar seu programa parado em outro thread. Isso ocorre sempre que outro thread encontra um breakpoint, sinal ou exceção antes de o primeiro thread concluir a operação solicitada.
O scheduler-locking fornecido pelo gdb pode ajudá-lo a bloquear o escalonador para permitir que apenas um único thread execute, modificando o comportamento padrão do GDB.
Por padrão, o valor de scheduler-locking é igual a replay
No modo de replay o comportamento é semelhante a on; no modo de gravação ou durante a execução normal, é semelhante a off. Este é o modo padrão.- on
Por exemplo, executar t 2 troca para o thread 2; ao definir on, a depuração acontece somente no thread atual.
- off
O oposto de on: sem bloqueio, qualquer thread pode executar a qualquer momento.
- step
Comporta-se como on durante a execução de passo único e, caso contrário, como off. Quando você dá um step, os threads diferentes do atual nunca têm chance de executar; já quando você usa comandos como continue, until ou finish, eles podem executar normalmente.
Por exemplo: comandos como next e step depuram dentro do processo que você quer, com os outros threads suspensos; já comandos como continue deixam todos os threads executarem.
Exemplo¶
Dá para ver que, com step definido, o continue alterna entre os threads.
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3de2 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
2 Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
* 3 Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
(gdb) c
Continuing.
[Switching to Thread 0x7ffff3409700 (LWP 31494)]
Thread 2 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399 size_t thread_index = thread_indexer_();
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3de2 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
* 2 Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
3 Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" 0x00007ffff605a372 in arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5bb060, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
2 Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
from /code/arrow/cpp/build/debug/libarrow.so.200
* 3 Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
from /code/arrow/cpp/build/debug/libarrow.so.200
(gdb) c
Continuing.
[Switching to Thread 0x7ffff3409700 (LWP 31494)]
Thread 2 "arrow-compute-n" hit Breakpoint 3, 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
from /code/arrow/cpp/build/debug/libarrow.so.200
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7e88780 (LWP 31464) "arrow-compute-n" 0x00007ffff37e3a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
* 2 Thread 0x7ffff3409700 (LWP 31494) "arrow-compute-n" 0x00007ffff5781b50 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
from /code/arrow/cpp/build/debug/libarrow.so.200
3 Thread 0x7ffff2c08700 (LWP 31495) "arrow-compute-n" 0x00007ffff5781b56 in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch(unsigned long, arrow::compute::ExecBatch)@plt ()
from /code/arrow/cpp/build/debug/libarrow.so.200
(gdb) t 2
[Switching to thread 2 (Thread 0x7ffff3409700 (LWP 32396))]
#0 0x00007ffff605ae0d in arrow::compute::NestedLoopJoinNode::OnBuildSideBatch (this=0x5bbf90, thread_index=1, batch=...) at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:487
487 if (!batch.length) {
(gdb) n
490 std::lock_guard<std::mutex> guard(build_side_mutex_);
(gdb) n
491 build_accumulator_.InsertBatch(std::move(batch));
(gdb) p batch.ToString()
[Switching to Thread 0x7ffff2c08700 (LWP 32397)]
Thread 3 "arrow-compute-n" hit Breakpoint 1, arrow::compute::NestedLoopJoinNode::InputReceived (this=0x5bbf90, input=0x5ba840, batch=...)
at /code/arrow/cpp/src/arrow/compute/exec/nested_loop_node.cc:399
399 size_t thread_index = thread_indexer_();
The program stopped in another thread while making a function call from GDB.
Evaluation of the expression containing the function
(arrow::compute::ExecBatch::ToString() const) will be abandoned.
When the function is done executing, GDB will silently stop.
(gdb) set scheduler-locking on
(gdb) p batch.ToString()
$2 = "ExecBatch\n # Rows: 2\n 0: Array[null,4]\n 1: Array[true,false]\n"
https://sourceware.org/gdb/current/onlinedocs/gdb.html/All_002dStop-Mode.html
Tópicos relacionados¶
- Guia de C++ Concurrency in Action | C++那些事
- Depuração de Deadlocks em C++ | C++那些事
- Vetorização SIMD em C++ | C++那些事
- STL Paralela do C++17 | C++那些事
- False Sharing e Linhas de Cache em C++ | C++那些事
- Detecção de Erros de Concorrência com Helgrind | C++那些事
- Princípio do event loop do libev | C++那些事
- Implementação interna de std::thread em C++ | C++那些事
- C++ std::thread e jthread | C++那些事
- Introdução a multi-thread em C++ | C++那些事