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

简介: 调试多线程应用不仅仅是具备一个能够管理多线程的调试器。许多现代调试器支持线程的执行和独立调试,并支持指定线程的断点、观察窗等。本节不讨论调试器的具体实现方法。例如,图3-13展示了DDD——GNU DeBugger(GDB)前端,它执行代码清单3-24中公平的读者–写者解决方案。

本节书摘来自华章出版社《多核与GPU编程:工具、方法及实践》一书中的第3章,第3.9节, 作 者 Multicore and GPU Programming: An Integrated Approach[阿联酋]杰拉西莫斯·巴拉斯(Gerassimos Barlas) 著,张云泉 贾海鹏 李士刚 袁良 等译, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

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,可以降低从日志中抽取信息的开销。


bf2bf63b7077096327ed857c1f90f09d7834879b

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

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

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

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

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

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


03c03125bd0576e84a71e2689990586e5839f829


a917a5baab2285fb4ffb9b9213114e87d92f8b91

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

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

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

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

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


9b5b507292e673b2438f5224a1a2963f35a3c29e

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

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


8e6ef39a98cd75de9798700d6396b2e5874fcdc4

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

相关实践学习
基于阿里云DeepGPU实例,用AI画唯美国风少女
本实验基于阿里云DeepGPU实例,使用aiacctorch加速stable-diffusion-webui,用AI画唯美国风少女,可提升性能至高至原性能的2.6倍。
相关文章
|
1月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
39 4
|
1月前
|
API 数据库 Android开发
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第14天】随着移动设备性能的日益强大,用户对应用程序的响应速度和流畅性要求越来越高。在Android开发中,合理利用多线程技术是提升应用性能的关键手段之一。Kotlin作为一种现代的编程语言,其协程特性为开发者提供了更为简洁高效的多线程处理方式。本文将深入探讨使用Kotlin进行Android多线程编程的最佳实践,包括协程的基本概念、优势以及在实际项目中的应用场景和性能优化技巧,旨在帮助开发者构建更加高效稳定的Android应用。
|
1月前
|
缓存 安全 Java
保障线程安全性:构建可靠的多线程应用
保障线程安全性:构建可靠的多线程应用
|
3月前
|
监控 Linux 编译器
多线程死锁检测的分析与实现(linux c)-有向图的应用
在日常的软件开发中,多线程是不可避免的,使用多线程中的一大问题就是线程对锁的不合理使用造成的死锁,死锁一旦发生,将导致多线程程序响应时间长,吞吐量下降甚至宕机崩溃,那么如何检测出一个多线程程序中是否存在死锁呢?在提出解决方案之前,先对死锁产生的原因以及产生的现象做一个分析。最后在用有向环来检测多线程中是否存在死锁的问题。
56 0
|
16天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【4月更文挑战第3天】 在Java并发编程中,线程池是一种重要的资源管理工具,它能有效地控制和管理线程的数量,提高系统性能。本文将深入探讨Java线程池的工作原理、应用场景以及优化策略,帮助读者更好地理解和应用线程池。
|
6天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
7天前
|
Java
探秘jstack:解决Java应用线程问题的利器
探秘jstack:解决Java应用线程问题的利器
15 1
探秘jstack:解决Java应用线程问题的利器
|
17天前
|
安全 Java 容器
Java并发编程:实现高效、线程安全的多线程应用
综上所述,Java并发编程需要注意线程安全、可见性、性能等方面的问题。合理使用线程池、同步机制、并发容器等工具,可以实现高效且线程安全的多线程应用。
14 1
|
28天前
|
消息中间件 Java 数据库连接
【C++ 多线程】C++ 多线程环境下的资源管理:深入理解与应用
【C++ 多线程】C++ 多线程环境下的资源管理:深入理解与应用
37 1
|
1月前
|
Java 开发者
深入理解Java并发编程:线程池的应用与优化
【2月更文挑战第29天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将首先介绍线程池的基本概念和原理,然后详细解析线程池的使用方法和注意事项,最后探讨如何优化线程池的性能。通过本文的学习,你将能够掌握线程池的核心知识,提高你的Java并发编程能力。