在上次的帖子
聊了C++多线程的跨平台问题,后来感觉意犹未尽。今天顺便说一下开发C++多线程应用程序时,有关调试和测试的一些注意事项。下面这些注意事项主要是针对C++,不过有些对于其它的语言也适用。<!-- program-think-->
★关于设置断点和单步执行
很多同学非常依赖于调试器的断点功能和单步功能。这在单线程情况下倒还好(不过有些单线程但涉及GUI的程序,也会有点麻烦)。至于多线程程序的调试,这两种手段简直就是噩梦的开始。多线程造成的主要问题大都和竞态条件(Race Condition,详细解释看“这里
”)有关。而设置断点或单步跟踪可能会严重干扰
多线程之间的竞争状态。导致你看到的是一个假象。比如本来有两个线程并发执行,存在某些不和谐的Bug(由竞态引起)。一旦你在某一个线程设置了断点,该线程在断点处停住了,只剩下另一个线程在跑。这时候,并发的场景已经完全被破坏了,你通过调试器看到的可能
是一个和谐的场景。
稍微跑一下题。这很类似量子力学的“测不准原理”,观测者的观测行为干扰了被测量的客体,导致观测者看到的是一个干扰后的现象。
★关于Log输出
既然断点和单步不好用。那咋办捏?一个替代方案是输出log日志。它可以有效减轻断点和单步所导致的(针对竞态条件的)副作用。
◇传统Log机制的问题
传统的log输出主要是打印到屏幕或者输出到文件。对于C++而言,标准库内置的类和函数(比如cout、printf、fputs)可能会有线程安全的问题(和编译器的具体实现有关)。尤其是标准流类库(iostream)的八个全局对象,更是要小心慎用。轻则输出的log文本混杂,重则导致程序崩溃。
鉴于上述原因,应该尽量使用第三方线程库内置的log机制来搞定log输出功能。比如ACE内置的ACE_Log_Msg等。
◇Log函数要短小精悍
很多情况下,我们会包装一个公用的函数来实现log输出功能。然后在该函数内部调用线程库的log类/函数。为了不影响线程的竞态条件,这个log函数要尽可能简单轻便:不要涉及太多杂七杂八的琐事、千万别进行耗时的操作、尽量不操作一些全局的变量。
◇Log的副作用
不过捏,即使log函数再短小精悍,也还是有可能影响竞态条件(毕竟log也有开销,也要消耗CPU时间)。
万一竞态条件受到log的影响,那就比较棘手了。我以前就碰到过这种情况:加了log,程序没有问题;去掉log,程序随机崩溃。这种情况一般有两种可能:要么是log功能本身有问题,要么是程序的竞态条件非常敏感(连log的开销都会有影响)。
这时候你能依靠的就只有肉眼和人脑了。先把相关的代码和文档仔细看上几遍(最好再找其他有经验的人一起Code Review),然后大家一起开动脑筋使劲琢磨。
★关于Debug版本和Release版本
C++程序经常有Debug版本和Release版本的区别。有些时候,这也会导致一些多线程的问题。
由于Debug版本包含了一些调试信息、启用了某些调试机制(比如assert宏)。所以就可能
影响到多线程的竞争状态。在倒霉的时候,会碰上Debug版本工作正常,Release版本程序随机崩溃。要避免这种情况,可以考虑下面两个办法:
◇放弃使用Debug版本
你可以干脆放弃使用Debug版本。在这种情况下,你需要考虑把诸如assert之类调试相关的宏替换成自己的一套宏,使得在非Debug版本下也可以生效。
◇两种版本同步测试
使用此方法,程序员平时自测可以使用Debug版本,但是测试人员日常测试的必须是Release版本。具体的操作步骤可以利用每日构建来辅助进行(每日构建的介绍参见“这里
”)。一定要避免:在平时仅仅搞Debug版本的测试,等到发布前夕再制作Release版本。这种做法是非常危险的!
★关于测试的机器(硬件)
说一个亲身经历、印象深刻的事情。
当年用ACE开发跨平台程序的时候,公司内的的开发环境和测试环境都是单CPU的机器。因为当时多核的机器还没有面世,多CPU的机器又挺贵,公司没舍得花钱配置。
软件开发完之后,测试人员经过几轮回归测试,也没发现太大问题。但是拿到客户的环境中运行,却经常会随机性崩溃。因为不能在客户环境中Debug,自己的环境又死活没问题,开发组的几个人只好充分发挥肉眼和人脑的功能(盯着代码和设计文档猛想)。经过N长时间,差点把脑袋想破,最后才意识到客户的机器是多CPU的。然后赶紧从其它部门借了一台多CPU机器,装上软件调试,最后查出是一个第三方库有问题。此事过后,我立即想出各种法子,去申请了几台多CPU机器给测试人员用。
由于上述的前车之鉴,所以我强烈建议:如果是开发多线程的应用程序,尽量给每一个
编程人员和测试人员都配置多核/多CPU的机器。毕竟现在多核机器已经很普及了,即使多CPU的机器,价格也还凑合。实在没必要为了省那点小钱而引入开发风险(不光会浪费开发/测试人员的时间,还可能增加实施和维护的成本)。
另外,可能有同学会问“超线程的机器如何捏?”关于多CPU
、多核
、超线程
三者之间的差异,有兴趣的同学可以看“这里
”。我个人感觉超线程不如多核与多CPU爽。
版权声明
本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者编程随想
和本文原始地址:
http://program-think.blogspot.com/2009/04/debug-test-multithreaded-applications.html
分享到:
相关推荐
4. **注意事项:** - 在使用`pthread_create`创建线程时,确保正确处理返回值以检查是否成功创建了线程。 - `pthread_join`函数用于等待线程结束,避免主线程提前退出导致子线程被异常终止。 - 在实际开发中,还...
提到的README.md文件通常是项目的说明文档,包括如何构建、运行、测试以及一些注意事项,对于理解和使用项目非常重要。 总的来说,这个项目综合运用了C++语言、Qt框架、多线程、网络编程等多个关键知识点,是一个...
- 在多线程环境中,错误处理和调试更为复杂,需要关注线程间的同步问题、资源竞争以及内存泄漏。 - 使用Visual Studio的调试工具,如线程视图和内存诊断,帮助找出潜在问题。 通过理解并实践这些知识点,你将能够...
5. **注意事项** - **内存管理**:在JNI中,需要手动管理内存,避免内存泄漏。使用`malloc()`和`free()`等函数进行分配和释放。 - **异常处理**:Java中的异常不能直接传递到C++,需要在JNI层进行捕获并处理。 - ...
本文将深入探讨“多线程高新”这一主题,结合黑马程序员的课程内容,来阐述多线程的核心概念、优势、实现方式以及在实际应用中的注意事项。 首先,多线程是指在一个进程中同时执行多个线程,这些线程可以共享同一...
本篇文章将深入探讨MFC多线程的开发,包括其原理、创建线程的方法以及注意事项。 多线程是现代计算机系统中并发执行任务的基本机制。在MFC中,线程是由CWinThread类表示的。通过继承CWinThread并覆盖其成员函数,...
11. **多线程调试**:在多线程应用中,可以使用“Thread”窗口来切换和观察不同线程的执行情况。 12. **跟踪输出**:使用`OutputDebugString()`函数可以在调试器的“Output”窗口中打印信息,便于分析程序运行过程...
- **代码编写与调试**:使用C++、C#或Python等语言,结合Halcon SDK实现多线程程序。 - **性能优化**:测试并调整参数,确保多线程的高效运行,避免资源浪费。 4. **挑战与注意事项**: - **线程安全**:确保在...
10. **调试多线程**:在多线程程序中,调试器可以标识当前执行线程,帮助理解和管理多个并发执行的路径。 11. **调试远程机器**:有时需要在目标机器上调试,Visual Studio支持远程调试,只需配置好调试服务器即可...
5. **注意事项** - 由于Android源码的复杂性,调试过程中可能会遇到各种问题,如库文件缺失、编译错误等,需要根据错误信息进行针对性的解决。 - 保持源码和编译环境的同步是非常重要的,否则可能会导致调试失败...
- **注意事项**:停止调试后,需要重新启动调试会话才能继续。 #### 三、总结 以上介绍了Visual C++ 6.0中与调试相关的常用快捷键及其使用场景。掌握这些快捷键可以帮助开发者更加高效地进行程序调试。在实际开发中...
- 如果DLL需要在多线程环境中使用,确保C++函数是线程安全的。C++/CLI可以帮助管理线程同步。 10. **文档和注释**: - 详细的文档和注释对于理解和维护这种跨语言的代码至关重要。解释每个接口的用途、参数和...
现代调试器如Visual Studio提供了丰富的多线程调试工具和视图,可以帮助开发者更容易地理解和诊断多线程程序中的问题。 在进行VC多线程编程时,除了上述技术细节,还应当了解操作系统对线程资源的调度策略,以及...
下面是一些关键步骤和注意事项: 1. **创建C++ DLL**:首先,你需要有一个C++工程,编译生成DLL文件。这个DLL文件包含可供C#调用的函数。确保函数的声明和定义遵循C语言的调用约定,因为这是.NET默认使用的。 2. *...
7. **注意事项**: - 必须在所有线程都结束之前释放TLS索引,否则可能会导致内存泄漏。 - TLS不适用于跨进程的数据共享,它仅限于同一进程内的线程。 - 确保在所有使用TLS的线程中正确处理异常情况,以防止资源未...
6. **多线程编程**:讲解如何在C++Builder中创建和管理多线程程序,提高程序的并发处理能力。 7. **单元测试与调试**:介绍使用C++Builder的内置单元测试框架进行测试,以及调试技巧和异常处理。 8. **性能优化**...