Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/125.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 多线程软件的测试方法_C++_Multithreading_Testing_Mfc - Fatal编程技术网

C++ 多线程软件的测试方法

C++ 多线程软件的测试方法,c++,multithreading,testing,mfc,C++,Multithreading,Testing,Mfc,我有一个成熟的地理空间软件,最近对这些区域进行了重写,以更好地利用现代PC机中可用的多处理器。具体而言,显示器、GUI、空间搜索和主处理都被划分为单独的线程。该软件有一个相当大的GUI自动化套件用于功能回归,另一个较小的GUI自动化套件用于性能回归。虽然所有的自动化测试都通过了,但我不相信它们在查找与竞争条件、死锁和其他与多线程相关的问题相关的bug方面提供了足够的覆盖率。您将使用什么技术来查看是否存在此类错误?假设有一些方法可以根除,你会提倡什么方法来根除它们 到目前为止,我所做的是在调试器下

我有一个成熟的地理空间软件,最近对这些区域进行了重写,以更好地利用现代PC机中可用的多处理器。具体而言,显示器、GUI、空间搜索和主处理都被划分为单独的线程。该软件有一个相当大的GUI自动化套件用于功能回归,另一个较小的GUI自动化套件用于性能回归。虽然所有的自动化测试都通过了,但我不相信它们在查找与竞争条件、死锁和其他与多线程相关的问题相关的bug方面提供了足够的覆盖率。您将使用什么技术来查看是否存在此类错误?假设有一些方法可以根除,你会提倡什么方法来根除它们

到目前为止,我所做的是在调试器下运行的应用程序上运行GUI功能自动化,这样我就可以打破死锁并捕获崩溃,并计划生成边界检查器,并针对该版本重复测试。我还通过PC Lint对源代码进行了静态分析,希望找到潜在的死锁,但没有得到任何有价值的结果

应用程序是C++、MFC、多文档/视图,每个DOC具有多个线程。我使用的锁定机制基于一个对象,该对象包含一个指向CMutex的指针,该指针在ctor中被锁定,在dtor中被释放。我使用这个对象的局部变量来根据需要锁定不同的代码位,我的互斥锁有一个超时,如果达到超时,就会发出警告。我尽量避免锁定,尽量使用资源副本

您还将进行哪些其他测试

编辑:我已经在许多不同的测试和编程论坛上交叉发布了这个问题,因为我渴望看到不同的思维方式和思想流派如何处理这个问题。所以,如果你看到它在别处交叉张贴,我表示歉意。大约一周后,我将提供一个摘要链接,链接到回应

并非真正的答案:

测试多线程bug非常困难。大多数bug只有在两个(或更多)线程以特定顺序进入代码中的特定位置时才会出现。 是否以及何时满足此条件可能取决于进程运行的时间。此正时可能会因以下前提条件之一而改变:

  • 处理器类型
  • 处理器速度
  • 处理器/核心数
  • 优化水平
  • 在调试器内部或外部运行
  • 操作系统
我确实忘记了更多的先决条件

因为机器翻译错误高度依赖于运行海森堡“不确定性原则”的代码的准确时间:如果你想测试机器翻译错误,你可以通过你的“措施”来改变时间,这可能会阻止错误的发生

时间因素使得机器翻译bug具有高度的不确定性。 换句话说:你的软件可能运行数月,然后某天崩溃,之后可能运行数年。如果您没有一些调试日志/核心转储等,您可能永远不知道它崩溃的原因

所以我的结论是:没有真正好的方法来进行线程安全的单元测试。编程时,您必须始终睁大眼睛

为了说明这一点,我将给你一个现实生活中的(简化的)例子(我在更换雇主和查看现有代码时遇到了这个问题):

想象你有一节课。如果没有人再使用该类,则希望该类自动删除。因此,在该类中构建一个引用计数器: (我知道在类的某个方法中删除类的实例是一种不好的风格。这是因为使用Ref类处理计数引用的实际代码的简化。)

这很简单,没什么好担心的。但这不是线程安全的! 这是因为“refcount++”和“refcount--”不是原子操作,但都是三种操作:

  • 从内存读取refcount到寄存器
  • 递增/递减寄存器
  • 将refcount从寄存器写入内存
这些操作中的每一个都可以被中断,另一个线程可以同时操作相同的refcount。因此,例如,如果两个线程想要增加refcount,可能会发生以下情况:

  • 线程A:从内存读取refcount到寄存器(refcount:8)
  • 线程A:增量寄存器
    • 语境变化-
  • 线程B:从内存读取refcount到寄存器(refcount:8)
  • 线程B:增量寄存器
  • 线程B:将引用计数从寄存器写入内存(引用计数:9)
    • 语境变化-
  • 线程A:将引用计数从寄存器写入内存(引用计数:9)
结果是:refcount=9,但它应该是10

这只能通过使用原子操作(即Windows上的InterlockedIncrement()和InterlockedDecrement())来解决

这个bug根本不稳定!原因是不太可能有两个线程同时尝试修改同一实例的refcount,并且代码之间存在上下文切换

但这是可能发生的!(如果您有一个多处理器或多核系统,这种可能性会增加,因为不需要进行上下文切换即可实现)。
它将在几天、几周或几个月内发生

虽然我同意@rstevens的回答,目前没有办法100%确定地对线程问题进行单元测试,但我发现有些东西很有用

首先,不管你做了什么测试,都要确保你在很多不同的规格盒上运行它们。我有几个构建机器,都是不同的,多核的,单核的,快的,慢的,等等。它们的多样性的好处是不同的机器会产生不同的线程问题。我经常去苏尔
class A {
  private:
    int refcount;
  public:
    A() : refcount(0) {
    }
    void Ref() {
      refcount++;
    }
    void Release() {
      refcount--;
      if (refcount == 0) {
        delete this;
      }
    }
};