静态线程分析:是个好主意吗?

27
我帮助维护和构建一个相当大的Swing GUI,其中包含许多复杂的交互。通常情况下,我会发现自己正在修复由于代码中其他地方存在某些竞态条件而导致进入奇怪状态的错误。
随着代码库变得越来越大,我发现它在通过文档规定哪些方法具有线程限制(最常见的是必须在Swing EDT上运行的方法)方面变得不那么一致。同样,如果知道并提供静态意识到哪些(我们自定义的)侦听器按规范在EDT上通知将非常有用。
所以我想这应该是可以使用注释轻松强制执行的内容。不出所料,至少存在一个静态分析工具CheckThread,使用注释来完成这项工作。它似乎允许您声明方法被限制为特定线程(最常见的是EDT),并将标记试图调用该方法但未声明自己被限制为该线程的方法。
因此,表面上看起来,这只是源代码和构建周期中低痛苦、高收益的补充。我的问题是:
  • 有没有人使用CheckThread或类似的库来强制执行线程约束的成功故事?有任何失败的故事吗?为什么会成功/失败?
  • 从理论上讲,这是好的吗?是否有理论上的缺点?
  • 从实践上来看,这很好吗?这值得了吗?它带来了什么价值?
  • 如果它在实践中有效,有哪些支持此功能的好工具?我只发现了CheckThread,但承认我不完全确定我要搜索什么以找到其他做同样事情的工具。
我知道它是否适合我们取决于我们的场景。但是我从来没有听说过人们在实践中使用类似的东西,老实说,从一些常规浏览中看起来它并没有得到很大的推广。所以我想知道原因。

顺便说一句,我很想听到相关的讨论,即使它与Java或EDT没有特定的关系。 - Mark Peters
我对Swing了解不够,因为我所有的时间都花在Servlet领域上...但编写不仅仅依靠运气而是线程安全的Servlet的唯一方法是在设计时非常小心地遵循某些常见模式。如果像这样的东西能鼓励您整理代码,那太好了。但我认为,在某个层面上,线程安全必须来自谨慎的编码。虽然这可能是现有代码库的一个不错的权宜之计。 - bwawok
我不确定我是否喜欢社区维基的赏金想法,但这个问题并没有得到太多关注,所以我还是要开始一个。目前我无法告诉您我将使用哪些确切标准来确定正确答案。 - Mark Peters
3个回答

8

这个答案更加关注您问题的理论方面。

从根本上讲,您正在做出一个断言:“此方法仅在某些线程下运行”。 这种断言与您可能做出的任何其他断言(“该方法仅接受小于17的整数作为参数X”)并没有什么不同。问题是:

  • 这些断言从哪里来?
  • 静态分析器能够检查它们吗?
  • 您从哪里获得这样的静态分析器?
大多数这样的断言必须来自软件设计师,因为他们是唯一知道意图的人。传统术语称之为“契约式设计”,尽管大多数DBC方案仅适用于当前程序状态(C的assert宏),而且它们应该真正适用于程序的过去和未来状态(“时间断言”),例如,“此例程将分配一个存储块,并最终某些代码将释放它”。可以构建工具,尝试启发式地确定断言是什么(例如Engler的断言归纳工作;其他人在这个领域也做了工作)。这很有用,但误报问题是一个问题。实际上,要求设计师编写这样的断言似乎并不特别繁琐,并且是非常好的长期文档。无论您是使用特定的“合同”语言结构编写此类声明,还是使用if语句(“if Debug && Not(assertion)Then Fail();”)或将其隐藏在注释中,都只是方便的问题。当语言允许直接编写此类声明时,这很好。

静态检查这种断言是困难的。如果你只考虑当前状态,那么静态分析器基本上必须对整个应用程序进行完全数据流分析,因为满足断言所需的信息很可能来自应用程序的另一部分创建的数据。(在你的情况下,“inside EDT”信号必须来自分析应用程序的整个调用图,以查看是否有任何调用路径将该方法从不是EDT线程的线程引导出)。如果使用时间属性,则静态检查需要一些状态空间验证逻辑; 这些目前仍然主要是研究工具。即使有了所有这些机制,静态分析器通常也必须在其分析中保守; 如果他们不能证明某些事情是假的,他们基本上必须假设它是真的,因为存在停机问题。

你在哪里获得这样的分析器?考虑到所需的所有机械设备,它们很难建造,因此你应该期望它们是罕见的。如果有人已经建造了一个,那太好了。如果没有……作为一般规则,你不想从头开始自己做这个。最好的长期希望是有通用的程序分析机器可用于构建这样的分析器,以分摊构建所有基础设施的成本。(我构建程序分析工具基础;请参阅我们的 DMS软件重构工具包)。
使构建此类静态分析器更“容易”的一种方法是将它们处理的情况限制在狭窄的范围内,例如CheckThread。我期望CheckThread会做现在它正在做的事情,并且不太可能变得更加强大。
“assert”宏和其他这样的动态“当前状态”检查流行的原因是它们实际上可以通过简单的运行时测试来实现。这非常实用。问题在于,你可能永远不会运行导致条件失败的路径。因此,对于动态分析,未检测到故障的缺失并不真正证明正确性。但仍然感觉良好。
底线:静态分析器和动态分析器各有其优势。

这是一个非常棒的解释,说明如何实现静态分析工具。幸运的是,它们似乎已经存在了。当谈到CheckThread时,您能否评论一下“限制它们处理的情况”是什么意思? - Mark Peters
关于运行时分析工具不能覆盖每个路径的问题...这也是我的想法。此外,除非使用字节码注入,否则为运行时检查添加仪器化相对困难。如果您想确保从EDT调用了每个Swing方法,则必须在调用任何Swing方法的每个方法中添加运行时检查。通过静态分析(特别是CheckThread),您可以配置该工具自动从这些方法向后工作。它甚至预配置了Swing在所有项目中都是常量。 - Mark Peters
@Mark Peters:我不确定CheckThread的具体限制。我对限制工具所做的评论基于多年来其他人和我自己构建的许多工具的经验;当你限制范围时,你最大化了成功的机会!通常,当你不得不从头开始构建基础设施时,你被限制范围,因为基础设施无法给你足够的支持。 - Ira Baxter
@Mark Peters:关于运行时检查实现:是的,你必须对许多路径进行仪器化。 (您可以使用字节码仪器; 您可能更普遍地进行源代码仪器化[DMS将执行此操作],因为大多数编程语言没有字节码。)而且我不确定您是否必须对每个Swing调用者进行仪器化;您所要做的就是仪器化所有Swing例程的前门,以检查它们是否在EDT线程的上下文中被调用。 - Ira Baxter

3

我们还没有尝试过任何静态分析工具,但是我们使用了AspectJ编写了一个简单的方面来检测在运行时是否在EDT之外调用了java.awt或javax.swing中的任何代码。它发现了我们代码中缺少SwingUtilities.invokeLater()的几个地方。我们在整个QA周期中启用此方面,然后在发布前不久关闭它。


运行时分析似乎也是件好事,如果与彻底的测试配对的话。我承认,在过去我开发时,我会编写一个checkEDT()方法,然后在提交之前删除它的出现。我想现实情况是,竞争条件并不改变代码是否在正确的线程上运行的问题。所以也许这已经足够了,静态分析并没有提供更好的结果?(除了文档和强制执行) - Mark Peters
1
这是使用AspectJ进行检查的好主意。能否分享代码? - Jayan

2

按照要求,这个内容并不专门涉及Java或EDT,但我已经看到了Coverity针对C/C ++的并发静态分析检查器取得了良好的效果。相比较较简单的检查器,它们确实具有更高的误报率,但鉴于线程错误在测试中很难发现,代码所有者似乎愿意接受这一点。细节是保密的,恐怕无法透露,但 Dawson Engler 的公开论文(例如,“Bugs as Deviant Behavior”)非常好地阐述了“你的代码中以下 'N' 个实例在执行 'Y' 之前都会进行 'X' 操作,而这个实例没有这样做”的一般方法。


我很喜欢阅读这篇论文,并已经查看了Coverity在并发方面提供的内容。Engler的研究很有趣,它努力在没有关于系统(系统“规则”等)的先验知识的情况下识别错误。我不知道自己对此有何感受,因为使用注释(从而将“规则”嵌入代码)的解决方案的吸引力之一是它将强制进行文档记录,同时防止错误。然而,这种哲学确实提供了一种简便的方法来发现错误,而无需首先创建明确的规则。这很容易实施。 - Mark Peters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接