Перейти к содержанию

Последнее обновление:2026-10-12

Отладка многопоточности GDB в режиме All-Stop

Когда мы отлаживаем многопоточную программу с помощью gdb, потоки могут выполняться в любой момент; когда мы пытаемся отладить конкретный поток (например, next), происходит переключение на другой поток, что не соответствует нашему ожиданию перейти на следующую строку в текущем потоке.

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_();
Что же нам делать?

Ответ — это и есть тема сегодняшнего раздела: блокировка планировщика (scheduler-locking). GDB не может выполнять все потоки в блокированном шаге. Поскольку планирование потоков зависит от операционной системы отлаживаемого объекта (а не контролируется GDB), за время, пока текущий поток выполняет один шаг, другие потоки могут выполнить несколько операторов. Кроме того, как правило, при остановке программы остальные потоки останавливаются посреди оператора, а не на чёткой границе операторов. Даже после continue или пошагового выполнения вы можете обнаружить, что программа остановилась в другом потоке. Это происходит всякий раз, когда другой поток наталкивается на точку останова, сигнал или исключение до того, как первый поток завершит запрошенное вами действие.

А предоставляемый gdb scheduler-locking помогает заблокировать планировщик, разрешив выполнение только одного потока, и тем самым изменить поведение GDB по умолчанию.

По умолчанию значение scheduler-locking равно replay

(gdb) show scheduler-locking
Mode for locking scheduler during execution is "replay".
В режиме воспроизведения поведение похоже on; в режиме записи или при обычном выполнении — похоже на выключенное. Это режим по умолчанию.

  • on

Например, выполнение t 2 переключает на поток 2; установка on заставит отлаживать только текущий поток.

  • off

Наоборот от on: блокировки нет, любой поток может выполняться в любой момент.

  • step

При пошаговом выполнении поведение похоже на включённое, иначе — на выключенное. Когда вы делаете step, потоки, кроме текущего, никогда не получают шанса выполниться, а когда вы используете команды вроде continue, until или finish, они полностью могут выполняться.

Например: next и step будут отлаживать в том процессе, который вы хотите, а остальные потоки зависают; команды вроде contine позволяют выполняться всем потокам.

Пример

Видно, что при установке step команда continue переключается между потоками.

(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
Затем очередной continue приведёт нас к следующей точке останова:

(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
Видно переключение между потоками; если нужно остаться внутри потока, можно просто сделать next, например:

(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));
Если же выполнить операцию print, вызывающую функцию, программа остановится и не выведет ожидаемый нами результат.

(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.
В этом случае мы можем установить on — и теперь print работает!

(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

Похожие темы

Комментарии