在 Visual Studio 中调试多线程应用程序

线程是操作系统向其授予处理器时间的指令序列。 在操作系统中运行的每个进程都包含至少一个线程。 包含多个线程的进程称为多线程。

具有多个处理器、多核处理器或超线程进程的计算机可以同时运行多个线程。 使用许多线程并行处理可以极大地提高程序性能,但是,由于会跟踪许多线程,也使得调试更加困难。

完全并行处理并非总是能够得以实现。 有时必须同步线程。 一个线程可能必须等待另一个线程的结果,或者一个线程可能需要独占访问另一个线程正在使用的资源。 同步问题是多线程应用程序中出现 Bug 的一个常见原因。 有时候,线程可能最终等待的是永远不可用的资源。 这会导致一种称为“死锁”的情况。

线程和进程

在计算机科学中,“线程”和“进程”是两个相关的概念 。 二者都表示必须按特定顺序执行的指令序列。 但是不同线程或进程中的指令可以并行执行。

进程存在于操作系统内,比如程序或应用程序。 而线程存在于进程内。 出于此原因,线程有时称为轻量进程。 每个进程都由一个或多个线程组成。

多个进程的存在使计算机能够同时执行多个任务。 而多个线程的存在使进程能够将工作分解、并行执行。 在多处理器计算机上,进程或线程可以在不同的处理器中运行。 这使真正的并行处理成为可能。

用于调试多线程应用的工具

Visual Studio 提供不同的工具用于调试多线程应用程序。

Visual Studio 还提供了功能强大的断点和跟踪点,在调试多线程应用程序时,它们十分有用。 使用断点条件和筛选器可将断点置于单个线程上。 使用跟踪点可以在不中断的情况下跟踪程序的执行,以研究诸如死锁之类的问题。 有关详细信息,请参阅断点操作和跟踪点

调试具有用户界面的多线程应用程序可能会特别困难。 可以考虑在另一台计算机上运行应用程序并使用远程调试。 有关详细信息,请参阅远程调试

下表显示了可用信息以及可在以上每个位置执行的操作:

用户界面 可用信息 可以执行的操作
“附加到进程”对话框 可以附加的可用进程:

- 进程名 (.exe)
- 进程 ID 号
- 菜单栏标题
- 类型(托管 v4.0;托管 v2.0、v1.1、v1.0;x86;x64;IA64)
- 用户名(帐户名)
- 会话号
选择要附加的进程

选择远程计算机

更改用于连接远程计算机的传输类型
“进程”窗口 附加的进程:

- 进程名称
- 进程 ID 号
- 进程 .exe 的路径
- 菜单栏标题
- 状态(中断、正在运行)
- 调试(本机、托管等。)
- 传输类型(默认、无身份验证时仅限本机)
- 传输限定符(远程计算机)
工具:

- 附加
- 拆离
- 终止

快捷菜单:

- 附加
- 拆离
- 调试停止时拆离
- 终止
“线程”窗口 当前进程中的线程:

- 线程 ID
- 托管 ID
- 类别(主线程、接口线程、远程过程调用处理程序或辅助线程)
- 线程名
- 创建线程的位置
- 优先级
- 关联掩码
- 挂起项计数
- 进程名称
- 标记指示器
- 挂起的指示器
工具:

- 搜索
- 搜索调用堆栈
- 标记“仅我的代码”
- 标记“自定义模块选择”
- 分组依据
- 列
- 展开/折叠调用堆栈
- 展开/折叠组
- 冻结/解冻线程

快捷菜单:

- 在源中显示线程
- 切换到线程
- 冻结正在运行的线程
- 解冻冻结的线程
- 标记一个线程以便进一步研究
- 取消标记线程
- 重命名线程
- 显示和隐藏线程

其他操作:

- 查看数据提示中一个线程的调用堆栈
源窗口 左侧滚动条槽中的线程指示符指示单线程或多线程(默认情况下处于关闭状态,可通过使用“线程”窗口中的快捷菜单打开) 快捷菜单:

- 切换到线程
- 标记一个线程以便进一步研究
- 取消标记线程
“调试位置”工具栏 - 当前进程
- 挂起应用程序
- 恢复应用程序
- 挂起并关闭应用程序
- 当前线程
- 切换当前线程标记状态
- 仅显示标记的线程
- 仅显示当前进程
- 当前堆栈帧
- 切换到另一个进程
- 挂起、恢复或关闭应用程序
- 切换到当前进程中的另一个线程
- 切换到当前线程中的另一个堆栈帧
- 标记或取消标记当前线程
- 仅显示标记的线程
- 仅显示当前进程
“并行堆栈”窗口 - 一个窗口中多个线程的调用堆栈。
- 每个线程的活动堆栈帧。
- 任何方法的调用方和被调用方。
- 死锁检测
- 筛选出指定的线程
- 筛选出外部代码堆栈
- 切换到“任务”视图
- 标记或取消标记线程
- 缩放
- 复制堆栈帧
- 将所有堆栈保存/导出为图像
“并行监视”窗口 - 标记列,可在其中标记要特别注意的线程。
- 帧列,其中箭头指示选定的帧。
- 可配置的列,可显示计算机、进程、平铺、任务和线程。
- 标记或取消标记线程
- 仅显示标记的线程
- 切换帧
- 对列进行排序
- 对线程进行分组
- 冻结或解冻线程
- 导出“并行监视”窗口中的数据
“任务”窗口 - 查看有关 Task 对象的信息,包括任务 ID、任务状态(已计划、正在运行、正在等待和已死锁)以及分配给任务的线程。
- 调用堆栈中的当前位置。
- 在创建时传递给任务的委托
- 切换到当前任务
- 标记或取消标记任务
- 冻结或解冻任务
“GPU 线程”窗口 - 标记列,可在其中标记要特别注意的线程。
- 当前线程列,其中黄色箭头指示当前线程。
-“线程计数”列,显示同一位置的线程数。
-“行”列,显示每组线程所在的代码行。
-“地址”列,显示每组线程所在的指令地址。
-“位置”列,表示该地址的代码中的位置。
-“状态”列,显示线程是活动的还是被阻止。
-“平铺”列,显示行中的线程的平铺索引。
- 更改到其他线程
- 显示特定平铺和线程
- 显示或隐藏列
- 按列排序
- 对线程进行分组
- 冻结或解冻线程
- 标记或取消标记线程
- 仅显示标记的线程