对我来说最难的事情之一是我第一次使用C中的pthreads进行强烈的编程。我习惯了解要运行的下一行代码是什么,我的调试技术以这种期望为中心。C编程:使用pthreads进行调试
什么是用C语言中的pthreads进行调试的一些好技术?您可以在没有任何附加工具,您使用的工具或其他任何可帮助您调试的情况下建议个人方法。
P.S.我在linux中使用gcc进行C编程,但不要让它一定限制你的答案
对我来说最难的事情之一是我第一次使用C中的pthreads进行强烈的编程。我习惯了解要运行的下一行代码是什么,我的调试技术以这种期望为中心。C编程:使用pthreads进行调试
什么是用C语言中的pthreads进行调试的一些好技术?您可以在没有任何附加工具,您使用的工具或其他任何可帮助您调试的情况下建议个人方法。
P.S.我在linux中使用gcc进行C编程,但不要让它一定限制你的答案
Valgrind是一个很好的工具,可以找到竞争条件和pthreads API滥用。它保留了一个程序内存模型(可能还有共享资源),并且即使在错误是良性的时候也会检测到缺失的锁(当然这意味着它在稍后的某个阶段会完全意外地变得不那么健康)。
要使用它,请调用valgrind --tool=helgrind
, here is its manual。此外,还有valgrind --tool=drd
(manual)。 Helgrind和DRD使用不同的模型,以便检测重叠但可能不同的一组错误。假阳性也可能发生。
无论如何,valgrind已经为我节省了无数个小时的调试时间(不是所有的人都在:)。
我倾向于使用大量的断点。如果你真的不关心线程函数,但是关心它的副作用,检查它们的好时机可能是正确的,然后退出或者回到它的等待状态或者它正在做的任何事情。
有关调试线程化程序会让你大吃一惊的是,当你在调试器中添加printf或运行程序时(通常称为Heisenbug),你会经常发现错误更改或甚至消失。
在一个线程化的程序中,Heisenbug通常意味着你有一个race condition。一个好的程序员会查找依赖于顺序的共享变量或资源。一个蹩脚的程序员会尝试用sleep()语句盲目修复它。
我对多线程调试方法类似于单线程的,但更多的时间是花在通常在思考阶段:
发展理论,以什么可能会造成问题。
确定如果理论是正确的话可以预期什么样的结果。
如有必要,添加可以反驳或验证您的结果和理论的代码。
如果您的理论是正确的,请解决问题。
通常,证明该理论的'实验'是在可疑代码周围添加临界区或互斥体。然后我会试着通过系统地缩小关键部分来缩小问题的范围。关键部分并不总是最好的解决方案(虽然通常可以是快速修复)。但是,它们对查明“吸烟枪”很有用。
就像我说过的,同样的步骤适用于单线程调试,尽管跳到调试器并且拥有它非常容易。多线程调试需要对代码有更深入的理解,因为我通常通过调试器发现正在运行的多线程代码不会产生任何有用的结果。
此外,hellgrind是一个伟大的工具。英特尔的线程检查器对Windows执行类似的功能,但其成本远远高于其他应用程序。
在'思考'阶段,在开始编码之前,请使用状态机概念。它可以使设计更清晰。
printf's可以帮助您了解程序的动态。但是他们混淆了源代码,所以使用宏DEBUG_OUT()并在其定义中使用布尔标志来启用它。更好的是,通过'kill -USR1'发送一个信号来设置/清除该标志。将输出发送到带有时间戳的日志文件。
也考虑使用assert(),然后使用gdb和ddd分析核心转储。
当我开始进行多线程编程时,我停止使用调试器。 对我来说,关键是好的程序分解和封装。
监视器是无错多线程编程的最简单方法。 如果你不能避免复杂的锁相关性,那么很容易检查它们是否循环。 - 等待程序挂起并使用'pstack'检查堆栈轨迹。 您可以通过引入一些新线程和异步通信缓冲区来打破循环锁定。
使用断言,并确保为软件的特定组件编写单线程单元测试 - 然后,如果需要,可以在调试器中运行它们。
我在多线程,高性能的世界中发展很快,所以这里是我使用的一般实践。
设计 - 最好的优化是一个更好的算法:
1)打破你的功能为逻辑上分离的部分。这意味着呼叫“A”并且只有“A” - 不是A然后B然后是C ...
2)没有副作用:取消所有裸露的全局变量,不管是否为静态。如果您不能完全消除副作用,请将其隔离到几个位置(将其集中在代码中)。
3)使尽可能多的隔离组件重新进入。这意味着它们是无状态的 - 它们将所有输入作为常量,并且只处理DECLARED,逻辑上常量的参数以产生输出。无论您身在何处,都可以通过价值而不是参考。
4)如果你有状态,在无状态的子组件和实际的状态机之间做一个清晰的分离。理想情况下,状态机将是一个操作无状态组件的单个函数或类。
调试:
线程错误往往进来2个广泛flavors-竞争和死锁。通常,死锁更具确定性。
1)您是否看到数据损坏?:是=>可能是一场比赛。
2)这个错误是出现在每次运行还是一些运行?:YES =>可能是死锁(比赛通常是非确定性的)。
3)进程是否挂起?:YES =>某处存在死锁。如果它只是偶尔挂起,你可能也会有一场比赛。
断点通常在代码中的作用与同步原语THEMSELVES非常相似,因为它们在逻辑上相似 - 它们强制执行在当前上下文中停顿,直到其他上下文(您)发送信号恢复。这意味着您应该查看代码中的任何断点,以改变其多线程行为,并且断点会影响竞争条件,但通常不会造成死锁。
通常,这意味着您应该删除所有断点,确定错误类型,然后重新引入它们以尝试修复它。否则,他们只是更多地扭曲事情。
+1因为我认为这是一个有很多'AHA!'的好问题 - 潜在的 – tr9sh 2009-06-11 13:36:22