ORB_SLAM2多线程调试:使用GDB追踪线程交互问题

ORB_SLAM2多线程调试:使用GDB追踪线程交互问题

【免费下载链接】ORB_SLAM2 Real-Time SLAM for Monocular, Stereo and RGB-D Cameras, with Loop Detection and Relocalization Capabilities 【免费下载链接】ORB_SLAM2 项目地址: https://gitcode.com/gh_mirrors/or/ORB_SLAM2

引言

SLAM(Simultaneous Localization and Mapping,同步定位与地图构建)系统在实时运行时面临着巨大的计算压力,ORB-SLAM2通过多线程架构有效提升了系统性能。然而,多线程带来的线程交互问题也成为调试中的一大挑战。本文将深入探讨ORB-SLAM2的多线程架构,分析常见的线程交互问题,并详细介绍如何使用GDB(GNU Debugger)进行多线程调试,帮助开发者快速定位和解决问题。

读完本文,你将能够:

  • 理解ORB-SLAM2的多线程架构和线程间交互机制
  • 识别常见的ORB-SLAM2线程交互问题及表现
  • 掌握使用GDB进行ORB-SLAM2多线程调试的技巧和方法
  • 学会设置断点、检查线程状态、追踪变量变化和分析死锁问题

ORB-SLAM2多线程架构解析

ORB-SLAM2采用了基于三个主要线程的并行架构,分别是Tracking(跟踪)线程、Local Mapping(局部建图)线程和Loop Closing(回环检测)线程。此外,还有一个可选的Viewer(可视化)线程用于实时显示系统运行状态。

线程架构 overview

mermaid

主要线程功能与交互

1. Tracking 线程

Tracking线程是ORB-SLAM2的主线程,负责实时处理输入的图像序列,提取ORB特征点,进行位姿估计和地图点跟踪,并在适当的时候创建新的关键帧。

// System.cc 中Tracking线程的初始化
mpTracker = new Tracking(this, mpVocabulary, mpFrameDrawer, mpMapDrawer,
                         mpMap, mpKeyFrameDatabase, strSettingsFile, mSensor);

Tracking线程与其他线程的主要交互:

  • 向Local Mapping线程传递新创建的关键帧
  • 接收来自Loop Closing线程的位姿校正信息
  • 与Viewer线程共享帧和地图信息用于可视化
2. Local Mapping 线程

Local Mapping线程负责处理新加入的关键帧,进行局部地图优化,包括三角化新的地图点、地图点筛选和局部Bundle Adjustment(光束平差法)。

// System.cc 中Local Mapping线程的初始化和启动
mpLocalMapper = new LocalMapping(mpMap, mSensor==MONOCULAR);
mptLocalMapping = new thread(&ORB_SLAM2::LocalMapping::Run,mpLocalMapper);

Local Mapping线程的核心循环:

// LocalMapping.cc
void LocalMapping::Run()
{
    mbFinished = false;

    while(1)
    {
        // Tracking will see that Local Mapping is busy
        SetAcceptKeyFrames(false);

        // Check if there are keyframes in the queue
        if(CheckNewKeyFrames())
        {
            // 处理新关键帧
            ProcessNewKeyFrame();
            
            // 地图点筛选
            MapPointCulling();
            
            // 三角化新地图点
            CreateNewMapPoints();
            
            // 局部BA优化
            Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap);
            
            // 关键帧筛选
            KeyFrameCulling();
            
            mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
        }
        // ... 线程同步和退出处理
    }
}
3. Loop Closing 线程

Loop Closing线程负责检测回环并执行回环校正,通过Sim3(相似变换)求解和位姿图优化来消除累积误差。

// System.cc 中Loop Closing线程的初始化和启动
mpLoopCloser = new LoopClosing(mpMap, mpKeyFrameDatabase, mpVocabulary, mSensor!=MONOCULAR);
mptLoopClosing = new thread(&ORB_SLAM2::LoopClosing::Run, mpLoopCloser);

Loop Closing线程的主要工作流程:

// LoopClosing.cc
void LoopClosing::Run()
{
    mbFinished =false;

    while(1)
    {
        // 检查是否有新的关键帧
        if(CheckNewKeyFrames())
        {
            // 检测回环候选
            if(DetectLoop())
            {
               // 计算相似变换
               if(ComputeSim3())
               {
                   // 执行回环校正
                   CorrectLoop();
               }
            }
        }       
        // ... 线程同步和退出处理
    }
}
4. Viewer 线程

Viewer线程负责实时可视化系统状态,包括当前帧、特征点、地图点和相机轨迹等信息。

// System.cc 中Viewer线程的初始化和启动
mpViewer = new Viewer(this, mpFrameDrawer,mpMapDrawer,mpTracker,strSettingsFile);
mptViewer = new thread(&Viewer::Run, mpViewer);

线程间同步机制

ORB-SLAM2使用互斥锁(mutex)、条件变量(condition variable)和标志位(flag)等同步机制来协调线程间的交互:

  1. 互斥锁:保护共享数据结构的访问,如地图、关键帧数据库等
  2. 条件变量:用于线程间的等待/通知机制,如等待新的关键帧
  3. 标志位:用于请求线程操作,如停止、重置等
// 互斥锁示例 (LocalMapping.cc)
void LocalMapping::InsertKeyFrame(KeyFrame *pKF)
{
    unique_lock<mutex> lock(mMutexNewKFs);
    mlNewKeyFrames.push_back(pKF);
    mbAbortBA=true;
}

// 标志位示例 (LoopClosing.cc)
void LoopClosing::RequestReset()
{
    unique_lock<mutex> lock(mMutexReset);
    mbResetRequested = true;
}

常见线程交互问题及表现

ORB-SLAM2的多线程架构虽然提高了系统性能,但也引入了复杂的线程交互问题。以下是几种常见的线程交互问题及其表现:

1. 数据竞争(Data Race)

当多个线程同时访问共享数据,且至少有一个线程进行写操作时,就可能发生数据竞争。在ORB-SLAM2中,地图(Map)和关键帧(KeyFrame)是最常见的共享数据。

表现症状

  • 系统运行不稳定,偶尔崩溃
  • 地图点或关键帧出现异常值
  • 定位结果突然跳变或漂移严重
  • 程序在不同运行时表现不一致

典型场景

  • Tracking线程正在读取地图点,同时Local Mapping线程正在删除或更新这些地图点
  • Loop Closing线程校正位姿时,其他线程正在访问或修改相同的关键帧

2. 死锁(Deadlock)

死锁发生在两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行的情况。

表现症状

  • 系统完全冻结,不再响应输入
  • CPU占用率异常低
  • 程序无法正常退出

典型场景

  • Tracking线程持有地图锁等待局部建图锁,而Local Mapping线程持有局部建图锁等待地图锁
  • Loop Closing线程在执行全局BA时获取多个关键帧锁的顺序不一致

3. 线程饥饿(Thread Starvation)

线程饥饿指一个或多个线程长时间无法获得所需资源,导致无法继续执行的情况。

表现症状

  • 某个功能模块(如回环检测)长时间不执行
  • 系统性能下降,处理帧率降低
  • 关键帧队列不断增长但得不到处理

典型场景

  • Local Mapping线程执行耗时的局部BA,导致无法及时处理新的关键帧
  • Loop Closing线程的全局BA占用大量计算资源,导致其他线程无法获得足够的CPU时间

4. 不一致的线程状态(Inconsistent Thread State)

当一个线程的操作依赖于另一个线程的状态,而状态信息没有正确同步时,会导致不一致的线程状态问题。

表现症状

  • 系统报"关键帧为空"或"地图点无效"等错误
  • 局部建图或回环检测功能异常关闭
  • 系统在特定操作(如重置)后行为异常

典型场景

  • Tracking线程向Local Mapping线程发送了重置请求,但在重置完成前又发送了新的关键帧
  • Loop Closing线程完成回环校正后,其他线程没有正确接收或应用校正结果

GDB多线程调试基础

GDB(GNU Debugger)是一个功能强大的命令行调试工具,支持多线程调试。本节介绍GDB多线程调试的基础知识和常用命令。

GDB多线程调试核心命令

命令功能描述
info threads显示所有线程信息
thread <id>切换到指定ID的线程
thread apply <id> <cmd>对指定线程执行命令
thread apply all <cmd>对所有线程执行命令
set scheduler-locking on锁定当前线程,只允许当前线程执行
set scheduler-locking off关闭线程锁定,允许所有线程执行
set scheduler-locking step在单步执行时锁定其他线程
break <location> thread <id>在指定线程的指定位置设置断点
break <location> thread <id> if <condition>在指定线程满足条件时设置断点
watch <expr> thread <id>为指定线程的表达式设置观察点

GDB调试ORB-SLAM2的基本流程

  1. 使用调试模式编译ORB-SLAM2
# 在ORB-SLAM2目录下
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j4
  1. 使用GDB启动ORB-SLAM2程序
gdb ./Examples/Monocular/mono_tum
  1. 设置命令行参数并运行
(gdb) set args Vocabulary/ORBvoc.txt Examples/Monocular/TUM1.yaml PATH_TO_SEQUENCE_FOLDER
(gdb) run
  1. 程序运行过程中,使用GDB命令进行调试

ORB-SLAM2多线程调试实战

1. 准备工作

在开始多线程调试前,需要确保ORB-SLAM2是在调试模式下编译的:

# 确保在ORB-SLAM2根目录
rm -rf build
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j4

2. 跟踪线程创建与状态

ORB-SLAM2的线程创建主要在System.cc中完成。我们可以在这些位置设置断点,跟踪线程的创建过程:

# 设置断点
(gdb) break System.cc:233  # Local Mapping线程创建
(gdb) break System.cc:238  # Loop Closing线程创建
(gdb) break System.cc:247  # Viewer线程创建

# 运行程序
(gdb) run

# 当程序停在断点时,查看线程信息
(gdb) info threads

查看线程调用栈,了解线程启动时的上下文:

# 切换到Local Mapping线程
(gdb) thread 2

# 查看调用栈
(gdb) backtrace

# 查看当前线程状态
(gdb) info frame

3. 调试数据竞争问题

假设我们怀疑在Local Mapping线程处理新关键帧时存在数据竞争问题,可以在LocalMapping::ProcessNewKeyFrame函数处设置断点,并观察共享数据的访问情况:

# 在LocalMapping::ProcessNewKeyFrame处设置断点
(gdb) break LocalMapping.cc:107

# 当断点命中时,锁定当前线程
(gdb) set scheduler-locking on

# 检查当前关键帧和地图点状态
(gdb) print mpCurrentKeyFrame
(gdb) print mpCurrentKeyFrame->GetMapPointMatches()

# 监视地图点数量变化
(gdb) watch mpMap->MapPointsInMap()

# 继续执行,当地图点数量变化时中断
(gdb) continue

4. 调试死锁问题

如果ORB-SLAM2在运行过程中出现冻结,很可能是发生了死锁。我们可以使用GDB附加到进程并检查各线程状态:

# 查找ORB-SLAM2进程ID
ps aux | grep ORB-SLAM2

# 使用GDB附加到进程
gdb -p <process_id>

在GDB中检查所有线程的状态和调用栈:

# 查看所有线程
(gdb) info threads

# 查看所有线程的调用栈
(gdb) thread apply all backtrace

# 检查各线程持有的锁
(gdb) thread apply all info locks

分析各线程的调用栈和持有的锁,可以识别出哪些线程在等待对方释放锁,从而定位死锁原因。

5. 调试回环检测线程交互

回环检测是ORB-SLAM2中最复杂的线程交互之一。我们可以跟踪Loop Closing线程与其他线程的交互:

# 在LoopClosing::CorrectLoop处设置断点
(gdb) break LoopClosing.cc:594

# 当断点命中时,检查当前回环候选关键帧
(gdb) print mpMatchedKF

# 查看回环校正前的位姿
(gdb) print mpCurrentKF->GetRotation()
(gdb) print mpCurrentKF->GetTranslation()

# 继续执行到回环校正后
(gdb) next
(gdb) next

# 查看回环校正后的位姿变化
(gdb) print mpCurrentKF->GetRotation()
(gdb) print mpCurrentKF->GetTranslation()

6. 高级:条件断点与数据监视

对于难以重现的偶发线程问题,可以使用条件断点和数据监视来捕获特定场景:

# 当关键帧数量超过50且回环检测线程活跃时中断
(gdb) break LoopClosing.cc:217 if mpMap->KeyFramesInMap() > 50 && mbRunningGBA

# 监视关键帧的位姿变化
(gdb) watch mpCurrentKF->GetPose()

# 当Tracking线程丢失跟踪时中断
(gdb) break Tracking.cc:345 if mState == LOST

线程交互问题的调试案例分析

案例1:Local Mapping线程导致的地图点丢失

问题描述:系统在运行一段时间后,Tracking线程突然报告大量地图点丢失,导致跟踪失败。

调试步骤

【免费下载链接】ORB_SLAM2 Real-Time SLAM for Monocular, Stereo and RGB-D Cameras, with Loop Detection and Relocalization Capabilities 【免费下载链接】ORB_SLAM2 项目地址: https://gitcode.com/gh_mirrors/or/ORB_SLAM2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值