Zircon 线程安全注解

Zircon 代码利用 Clang 的线程安全性分析功能 记录和机器验证一些同步不变量。这些 在针对 Clang 进行构建时,会检查注解(请参阅 使用入门 clang)。

使用方法

Clang 的文档

在 Zircon 中,我们提供自己的一组宏,用于封装注释, 为我们的同步基元添加了注解。在编写涉及 同步或为现有代码添加注释,在大多数情况下,您应该使用 由 Cloud Build 提供的线程注解宏 <lib/zircon-internal/thread_annotations.h. 这些宏都以前缀 "TA_" 开头,用于线程分析。最 常用选项包括:

  • TA_GUARDED(x):带注解的变量受 capability(例如锁)保护x
  • TA_ACQ(x...) 函数获取 x 集中的所有互斥量并在返回后保留它们
  • TA_REL(x...) 函数释放 x 集合中的所有互斥量
  • TA_REQ(x...) 函数要求调用方保留 x 集中的所有互斥量。
  • TA_EXCL(x...) 函数要求调用方不得持有 x 组中的任何互斥量

例如,包含成员变量'int foo_'的类受 互斥量的注解将如下所示:

// example.h

class Example {
public:
    // Public function has no locking requirements and thus needs no annotation.
    int IncreaseFoo(int by);

private:
    // This is an internal helper routine that can only be called with |lock_|
    // held. Calling this without holding |lock_| is a compile-time error.
    // Annotations like TA_REQ, TA_ACQ, TA_REL, etc are part of the function's
    // interface and must be on the function declaration, usually in the header,
    // not the definition.
    int IncreaseFooLocked(int by) TA_REQ(lock_);

    // This internal routine requires that both |lock_| and |foo_lock_| be held by the
    // caller.
    int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_) TA_REQ(bar_lock_);

    // The TA_GUARDED(lock_) annotation on |foo_| means that |lock_| must be
    // held to read or write from |foo_|.
    int foo_ TA_GUARDED(lock_);

    // |lock_| can be declared after annotations referencing it,
    // if desired.
    Mutex lock_;

    Mutex bar_lock_;
};

// example.cpp

int Example::IncreaseFoo(int by) {
    int new_value;
    {
        AutoLock lock(&lock_);  // fbl::AutoLock is annotated
        new_value = IncreaseFooLocked(by);
    }
    return new_value;
}

请注意,对于允许成组互斥对象的注释, 多次应用注释,也可以为 注解。换句话说,以下两个声明是等效的。

    int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_) TA_REQ(bar_lock_);
    int IncreaseFooAndBarLocked(int foo_by, int bar_by) TA_REQ(lock_, bar_lock_);

通过 sysroot 公开的库代码必须使用更糟糕的名称 宏的 system/public/zircon/compiler.h 更改为 避免与 sysroot 的使用者发生冲突。

最佳做法

注解应该对注解和标识符进行补充, 易于理解。注释不会取代注释或清除名称。请尝试 在编写涉及锁定的代码时,请遵循以下最佳实践:

  • 受锁和锁保护的群组成员变量。影响因素 除了 注释。例如,多个成员变量受一个锁保护时 和多个注释都由一种不同的锁保护,因此注释比 会逐一讲解每个注释

  • 命名需要锁的函数使用“Locked()”存储后缀。如果有 为调用函数而可能持有的多个锁,请考虑 并在函数名称中明确说明选择谨记致电者 代码将无法看到注释。

限制

线程安全分析是在编译时执行的纯静态检查, 无法理解有条件持有的锁,或跨越多个 编译单元。在许多 此分析仍然有用,但也存在一些 根本无法理解。停用分析的主要应急路径 向函数定义中添加注解 TA_NO_THREAD_SAFETY_ANALYSIS 包含容易导致分析混淆的代码。其他转义机制包括 - 请参阅 Clang 文档了解详情。导致 因为这对人类理解可能很复杂,所以需要停用分析 而且应该附带注释,指示 使用中的不变性。

线程安全分析可能在很多方面失效,例如, 使用指针时的注意事项例如,在获取受保护数据的地址时, 成员 Clang 无法跟踪 Guard,例如适用于受锁保护的 foo_ 在没有持有锁的情况下对 memset(&foo_, 0, sizeof(foo_)) 的调用不会被捕获 视为违规行为。