《多核与GPU编程:工具、方法及实践》---- 3.9 调试多线程应用

3.9 调试多线程应用

调试多线程应用不仅仅是具备一个能够管理多线程的调试器。许多现代调试器支持线程的执行和独立调试,并支持指定线程的断点、观察窗等。本节不讨论调试器的具体实现方法。例如,图3-13展示了DDD——GNU DeBugger(GDB)前端,它执行代码清单3-24中公平的读者–写者解决方案。在UNIX/Linux中能够使用DDD和GDB(对于CLI困难)的唯一要求是在编译程序时添加调试支持选项,亦即使用编译器的-g开关。

多线程程序中的bug通常只有在与事件的某个精确时序有关的特定环境下才会出现。调试器通过暂停或者降低线程执行速度干扰了线程时序,使得bug的重现和发现是一项具有挑战性的工作。最终,发现bug是一项需要经验和直觉的任务,但是适当地植入程序代码可以帮助简化调试工作。

下面的列表中枚举了程序员为确保一个无bug的多线程应用应该遵循的步骤。

消除多线程应用中bug的第一步当然是不要在最初将其加入到程序中。在编写代码之前,一个合理的软件设计方案是十分关键的。

本章研究的经典问题不仅仅具有教学意义。实际应用中遇到的大部分并发问题,或者是这些问题的实例,或者可以通过简单的变换将其归约到这些问题上。使用本章介绍的解决方案可以避免重新设计一个新方案所带来的问题。
应用程序应该被修改为支持生成某种可以离线处理的日志或者足迹历史信息。这允许收集有关应用程序运行时行为的信息。

在应用程序中拥有过量的线程会使得生成的日志信息难于理解。就线程个数来说,将应用程序参数化一般是一个好的设计。通过限制线程数目为1,可以发现与时序无关的bug。将线程数目设置为2或者3,可以降低从日志中抽取信息的开销。

通过printf和cout语句来维护执行路劲以及程序状态是不够的。在同一时刻使多线程生成控制台(或者文件)输出经常会导致消息的混乱以致无法解码。解决方法是将控制台(或者文件流)作为一个共享资源对待,并且将输出语句放置到关键区中。

一种方式是将调试和追踪信息放置到内存缓冲区中(称为足迹缓冲区),并在程序终止时保存。这是一种有问题的方式,可能导致程序的错误行为。这需要程序(a)正常终止(亦即没有发生崩溃或者挂起),(b)缓冲区足够大,能存储所有生成的信息,以及(c)缓冲区没有被内存错误影响。

一个更好的解决方案是把足迹消息尽可能快地转储到控制台中。这种方式也适用于文件,但是对每一条需要保存的消息引入了打开和关闭文件的开销。否则,如果程序崩溃,可能导致文件最近更改及重要信息的丢失。

为了区分正常的程序输出和足迹消息,可以利用标准错误输出流。但是当需要进一步处理调试输出时,例如根据生成消息的线程来过滤消息等处理,又该如何设计?解决方案十分简单,即通过流重定向。这是*nix和Windows环境中的一个通用功能。因此,为了将标准错误输出重定向到trace.log文件中,可以使用下面的语句:

为所有调试消息打上其生成时间的时间戳也是一个好主意。为了实现这一目的,分辨率在1毫秒或者更低的正常时间函数(例如clock)就不能满足需求了。需要一个更高分辨率的定时器,这可以通过不同的API实现(更多细节请参考附录C.2节)。在本节后面的部分假定存在一个称为hrclock的函数(高分辨率时钟),它可以返回一个双精度类型的时间戳。

代码清单3-30 展示了一个实现前面讨论的方案的示例。

这个示例程序的关键点如下。

附加的代码片段位于C++的预处理条件块中(第33~37行,以及第47~49行)。只需要将第三行注释掉就可以得到该程序的一个发布版本。

使用一个全局的互斥量来保证debugMsg函数体位于关键区中。

从程序开始执行时开始测量时间。实例的时间戳存储在time0变量中,接下来每一步都从每个计算的时间戳减去它(第15行)。

该程序的一次简单运行以及对其调试输出的仔细观察可以揭示竞争条件的工作方式。

最终是一些有关恰当调试程序的警告:应该关闭编译器优化。优化编译器可能为了使得执行流更顺畅,而改变语句执行顺序,或者甚至丢弃在程序中声明的变量。因此会导致调试一个优化的程序产生奇怪的结果,例如语句间跳转。在极端情况下,可能会由于编译器的优化而导致一个bug。尽管这是异常的事件,但是还有一些编译器优化是被标识为“不安全”的。尝试将编译器发挥到极致的程序员应该确保最终应用程序的行为与未优化的行为是一致的。

例如,GCC的-ftree-loop-if-conver-stores编译选项将条件内存写操作变为一个无条件内存写操作。从编译器手册中选择的一个示例如下:

如果数组A是一个共享资源,则两个版本的代码都可能会引入竞争条件。在后一个版本中,问题可能会被放大。

时间: 2024-08-23 13:31:29

《多核与GPU编程:工具、方法及实践》---- 3.9 调试多线程应用的相关文章

《多核与GPU编程:工具、方法及实践》----第2章 多核和并行程序设计 2.1 引言

第2章 多核和并行程序设计 本章目标 学习设计并行程序的PCAM方法. 使用任务图和数据依赖图来识别可以并行执行的计算部分. 学习将问题的解法分解为可并发执行部分的流行的分解模式. 学习编写并行软件的主要程序结构模式,如主/从和fork/join. 理解分解模式的性能特点,如流水线. 学习如何结合分解模式和合适的程序结构模式. 2.1 引言 即使是对于经验丰富的专业程序员,向多核编程的过渡也并不简单.多核和并行编程往往会打破语句按严格顺序执行的串行程序的传统风格.当许多事情在同一时间发生时,正如

《多核与GPU编程:工具、方法及实践》----导读

目 录[第1章 概述 1.1 多核计算机时代 ](https://yq.aliyun.com/articles/90097)1.2 并行计算机的分类[1.3 现代计算机概览 1.3.1 Cell BE处理器 1.3.2 NVIDIA Kepler 1.3.3 AMD APU 1.3.4 从多核到众核:Tilera TILE-Gx8072和Intel Xeon Phi ](https://yq.aliyun.com/articles/90111)1.4 性能指标[1.5 并行程序性能的预测与测量

《多核与GPU编程:工具、方法及实践》----2.3 分解模式

2.3 分解模式 设计过程最困难同时也最关键的部分无疑是分解过程,即确定可以并发执行的计算.虽然任务图法是最常用的,但开发者无法从中获取以往的经验,这时就需要模式.Mattson等人[33]列出了若干分解模式(在他们的书中表示为"algorithm structure design space patterns"), 该参考文献包含了工作负载被分解并最终分配到并行或多核平台各个节点上的基本方法.图2-4显示了能得到6个模式之一的决策树. 上一节提到了两类分解,即功能分解和域分解,现在又

《多核与GPU编程:工具、方法及实践》----2.4 程序结构模式

2.4 程序结构模式 模式不仅可以帮助选择合适的工作负载分解方法,还可用于程序的开发,这正是程序结构模式的目标.接下来的一节将讨论和分析几个最著名的模式. 并行程序结构模式可以分为两大类. 全局并行局部串行(Globally Parallel Locally Sequential,GPLS):GPLS表示应用程序可以并发执行多个任务,每个任务串行执行.这类模式包括: 单程序多数据 多程序多数据 主/从 map-reduce 全局串行局部并行(GSLP):GSLP表示应用程序串行执行,当需要时,一

《多核与GPU编程:工具、方法及实践》----1.5 并行程序性能的预测与测量

1.5 并行程序性能的预测与测量 构建并行程序要比串行程序更具挑战性.并行程序程序员需要解决诸如共享资源访问.负载均衡(即,将计算负载分配到所有计算资源上来最小化执行时间)以及程序终止(即,以协调方式暂停程序)等相关问题. 编写并行程序应该首先确定并行能否提升程序性能,加速问题的解决.并行程序的开发成本决定了程序员不可能简单地实现多个并行版本,通过测试找出最佳和最差版本,来评估项目的可行性.虽然对于最简单的问题,这个方法可能是可行的.但是即使能够这样,如果能够确定一个先验的最佳开发路径,并按照这

《多核与GPU编程:工具、方法及实践》----第1章 概 述 1.1 多核计算机时代

第1章 概 述 本章目标: 了解计算机(计算机体系架构)设计的发展趋势以及该趋势如何影响软件开发. 学习基于Flynn分类的计算机分类方法. 学习评估多核/并行程序性能即加速比和效率的必备工具. 学习测量和报告程序性能的正确实验方法. 学习Amdahl和Gustafson-Barsis定律,并使用这两个定律预测并行程序性能. 1.1 多核计算机时代 在过去的40年中,数字计算机已经成为技术和科学发展的基石.遵循20世纪70年代摩尔(Gordon E. Moore)发现的摩尔定律,计算机的信息处理

《多核与GPU编程:工具、方法及实践》----3.2 线程

3.2 线程 3.2.1 线程的定义 线程可以被认为是轻量级进程.更精确的定义是线程是一个执行路径,亦即一个指令序列,可以被操作系统作为整体单元进行管理调度.一个进程中可以有多个线程. 线程可以减轻原有生成进程机制中的开销,仅需要拷贝基本的数据,即运行栈.由于线程包括被调用函数的动态框架(或动态记录),因此运行栈不能被多个线程共享.共享栈意味着控制权可能返回一个与调用线程不同的位置. 当一个进程的主线程(初始线程)生成一个新线程时,最终的内存布局十分类似于图3-3.这里父线程和子线程的关系也是如

《多核与GPU编程:工具、方法及实践》----1.4 性能指标

1.4 性能指标 发展多核硬件和开发多核软件的目标是获取更高性能,例如更短的执行时间.更大规模的问题和更大的数据集等.很明显,这需要一个客观的标准或者准则来评估这些努力的有效性. 最起码,一个并行程序的执行时间应该要比其对应的串行程序的执行时间短(然而,并不是所有情况都这样).执行时间的缩短通常表述为加速比,可用如下公式表达: (1-1) 其中,对于解决同一个问题实例,tseq是串行程序的执行时间,tpar是并行程序的执行 时间. tseq和tpar都是时间,但它们并不总客观.二者会受到如下因素

《多核与GPU编程:工具、方法及实践》----第3章 共享内存编程:线程 3.1 引言

第3章 共享内存编程:线程 本章目标: 学习线程的定义以及创建方法. 学习完成特定任务的初始化线程方法. 学习多种终止多线程程序的技术. 理解多线程访问共享数据过程中的主要问题,例如竞争和死锁. 学习信号量和监视器的定义和使用方法. 熟悉经典同步问题及其解决方法. 学习运行时动态管理线程. 学习多线程程序的调试技术. 3.1 引言 从20世纪60年代麻省理工学院引入兼容分时系统(CTSS)以来,多个程序并发执行的现象已经变得较为常见.操作系统通过中断当前正在执行的程序并将CPU的控制权交由另一个