visualstudio中的并发代码分析

与并发错误的斗争对C++开发者构成了严峻的挑战。多核和多核体系结构的出现加剧了这个问题。为了应对多线程软件日益增加的复杂性,有必要使用更好的工具和进程来帮助开发人员遵守适当的锁定规则。在这篇博文中,我们将介绍VisualStudio2019预览版2附带的一个完全恢复活力的并发代码分析工具集。

null

险恶的景色

目前使用的最流行的并发编程范式是基于线程和锁的。业界的许多开发人员都在多线程软件上投入了大量资金。不幸的是,由于缺乏成熟的多线程软件开发包,开发和维护多线程软件非常困难。

开发人员经常面临如何使用同步的两难境地。过多可能导致死锁和性能低下。太少可能导致竞争条件和数据不一致。更糟糕的是,Win32线程模型引入了额外的陷阱。与托管代码不同,锁获取和锁释放操作不需要在C/C++中进行语法范围。因此,应用程序容易发生不匹配的锁定错误。一些Win32 API有微妙的同步副作用。例如,如果不小心使用,流行的“SendMessage”调用可能会导致UI线程之间挂起。因为并发错误是间歇性的,所以它们是测试过程中最难发现的错误之一。遇到这种情况时,很难重现和诊断。因此,在工程过程中尽早应用有效的多线程编程准则和工具是非常有益的。

锁定纪律的重要性

由于通常不能控制由线程交错引起的所有转角情况,因此在编写多线程程序时必须遵守某些锁定规则。例如,在获取多个锁或一致使用std::lock()时遵循锁顺序有助于避免死锁;在访问共享资源之前获取适当的保护锁有助于防止争用情况。然而,这些看似简单的锁定规则在实践中却难以遵循。

当今编程语言的一个基本限制是它们不直接支持并发需求的规范。程序员只能依赖非正式文档来表达他们关于锁使用的意图。因此,开发人员显然需要实用的工具来帮助他们自信地应用锁定规则。

并发工具集

为了解决C/C++在并发支持中的不足,我们在Visual Studio 2012中重新发布了一个初始版本的并发分析器,并给出了有希望的初始结果。此工具对最常见的Win32锁定API和并发相关注释有基本了解。

在VisualStudio 2019预览2中,我们兴奋地宣布一个完全更新的并发检查集,以满足现代C++程序员的需要。该工具集包含一个本地过程内锁分析器,内置了对常见Win32锁定原语和API、RAII锁定模式和STL锁的理解。

入门

并发检查作为visualstudio中代码分析工具集的一部分进行了集成。项目的默认“Microsoft本机推荐规则集”包含来自并发分析器的以下规则。这意味着,无论何时在项目中运行代码分析,都会自动执行这些检查。这些检查也会作为项目背景代码分析运行的一部分自动执行。对于每个规则,您可以单击链接以了解有关规则及其实施的详细信息,并提供清晰的示例。

  • C26100型 :竞赛条件。变量应该由锁保护。
  • C26101型 :未能正确使用变量的联锁操作。
  • C26110型 :调用者在调用一个函数之前未能获取锁,该函数期望在被调用之前获取锁。
  • C26111型 :调用者在调用一个函数之前未能释放一个锁,该函数期望在被调用之前释放该锁。
  • C26112型 :调用者在调用一个函数之前不能持有任何锁,该函数要求在被调用之前不持有任何锁。
  • C26115型 :未能释放函数中的锁。这将引入孤立锁。
  • C26116型 :未能获取函数中的锁,该函数应获取该锁。
  • C26117型 :释放函数中未焊接的锁。
  • C26140型 :在锁上指定的未定义的锁类型。

如果您想试用此检查器中的完整规则集,则有一个规则集可供使用。您必须右键单击项目>属性>代码分析>常规>规则集>选择“并发检查规则”。

Screenshot of the Code Analysis properties page that shows the ConcurrencyCheck Rules ruleset selected.

您可以通过在我们的文档中搜索范围为C26100–C26199的规则编号来了解有关检查器强制执行的每个规则的更多信息 C/C++告警代码分析 文件。

运行中的并发工具集

并发工具集的初始版本能够发现与并发相关的问题,如竞争条件、锁定副作用,以及大部分类似C的代码中的潜在死锁。

该工具内置了对标准Win32锁定API的理解。对于具有锁定副作用的自定义函数,该工具可以理解许多与并发相关的注释。这些注释允许程序员表达锁定行为。下面是一些与并发相关的注释示例。

  • _获取锁(lock):函数获取锁对象“lock”。
  • _释放锁定:函数释放锁定对象“lock”。
  • _需要锁定保持锁定(lock):进入此函数前必须获取锁定对象“lock”。
  • _由(锁)数据保护:“数据”必须始终由锁对象“锁”保护。
  • _Post_same_lock(lock1,lock2):“lock1”和“lock2”是别名。

有关完整的并发相关注释集,请参阅本文 注释锁定行为 .

工具集的更新版本通过将其分析能力扩展到现代C++代码而建立在初始版本的优势之上。例如,它现在可以理解STL锁和RAII模式,而不必添加任何注释。

既然我们已经讨论了checker是如何工作的,以及如何在项目中启用它们,那么让我们来看一些实际的例子。

例1

你能找出这个代码的问题吗 1 ?

struct RequestProcessor {​
    CRITICAL_SECTION cs_;​
    std::map<int, Request*> cache_;​
​
    bool HandleRequest(int Id, Request* request) {​
        EnterCriticalSection(&cs_);​
        if (cache_.find(Id) != cache_.end()) ​
            return false;    ​
        cache_[Id] = request;                    ​
        LeaveCriticalSection(&cs_);   ​
    }​
    void DumpRequestStatistics() {​
        for (auto& r : cache_) ​
            std::cout << "name: " << r.second->name << std::endl;​
    }​
};​

1 如果你见过 Anna Grinsrunt在2018年CppCon大会上发表的演讲,您可能对这段代码很熟悉。

让我们总结一下这里发生了什么:

  1. 在功能上 HandleRequest ,我们得到锁 cs 在6号线。但是,我们从8号线很早就回来了,从来没有松开过锁。
  2. 在功能上 HandleRequest ,我们看到了 cache_ 必须用锁保护通道 cs . 但是,在另一个函数中, DumpStatistics ,我们访问 cache_ 没有锁。

如果在此示例上运行代码分析,将在方法中得到警告 HandleRequest ,它将投诉泄漏的关键部分(问题1):

This shows the leaked critical section warning from the concurrency analyzer.

接下来,如果您添加 _Guarded_by_ 现场注释 cache_ 并选择ruleset“并发检查规则”,您将在方法中得到一个额外的警告 DumpRequestStatistics 对于可能的竞争条件(问题2):

This shows the potential race condition warning from the concurrency analyzer.

例2

让我们看一个更现代的例子。你能找出这个代码的问题吗 1 ?

struct RequestProcessor2 {​
    std::mutex​ m_;
    std::map<int, Request*> cache_;​
​
    void HandleRequest(int Id, Request* request) {​
        std::lock_guard grab(m_);
        if (cache_.find(Id) != cache_.end()) ​
            return;    ​
        cache_[Id] = request;                    ​
    }​
    void DumpRequestStatistics() {​
        for (auto& r : cache_) ​
            std::cout << "name: " << r.second->name << std::endl;​
    }​
};

一如所料,我们没有收到任何警告 HandleRequest 在上述实现中使用 std::lock_guard . 不过,我们还是收到了警告 DumpRequestStatistics 功能:

This shows the potential race condition warning from the concurrency analyzer.

在幕后发生了一些有趣的事情。首先,检查器了解 std::mutex . 第二,它明白这一点 std::lock_guard 保存互斥体,并在其作用域结束时在销毁过程中释放它。

此示例演示了重新启用的并发检查器的一些功能及其对STL锁和RAII模式的理解。

给我们反馈

我们很想听听您使用新并发检查的经验。记住切换到项目的“并发检查规则”来探索工具集的全部功能。如果您希望我们在编译时检测特定的并发模式,请告知我们。

如果您对此检查或任何Visual Studio功能有任何建议或问题,请 报告问题 或张贴在 开发者社区 . 我们还在推特上 @视觉 .

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0 分享