【译】clang ThreadSafetyAnalysis 线程安全分析

每天晚上临睡前一到两小时,前后花了一两周来试着翻译。过程是痛苦的,却是一个检视自己的好办法。
放在git上,正在考虑以后把随笔迁到gitio上。

https://releases.llvm.org/3.5.0/tools/clang/docs/ThreadSafetyAnalysis.html

clang3.5线程安全分析

名词

Program point:程序点。程序执行的位置。

False negative:假阴 看起来没问题,其实有问题。

False positive:假阳 看起来有问题,其实没问题。

Inter-procedural:过程间的

Intra-procedural:过程内的

简介

Clang线程安全分析是c++的一个语言扩展功能,用来对代码中的竞态条件作出警告。分析完全是静态的(只消耗编译时间),没有运行时开销。这项功能由Google开发,虽然目前还在开发中,但已经足够成熟,完全可以在生产环境下使用,Google内部就广泛地使用着。

在多线程程序中,线程安全分析就像类型系统一样。除了声明数据的类型(比如说int, float)外,码农还可以声明如何控制数据的访问。打个例子,如果foo由互斥量mu守卫,在没有锁定mu的情况下读写foo,线程安全分析会报一个警告。同样地,如果存在一些特定的例程(routines),这些例程只能由GUI线程调用,而其他线程调用了这些例程,那线程安全分析也会报警告。

开始使用

#include "mutex.h"

class BankAccount {
private:
  Mutex mu;
  int   balance GUARDED_BY(mu);

  void depositImpl(int amount) {
    balance += amount;       // WARNING! Cannot write balance without locking mu.
  }

  void withdrawImpl(int amount) EXCLUSIVE_LOCKS_REQUIRED(mu) {
    balance -= amount;       // OK. Caller must have locked mu.
  }

public:
  void withdraw(int amount) {
    mu.Lock();
    withdrawImpl(amount);    // OK.  We've locked mu.
  }                          // WARNING!  Failed to unlock mu.

  void transferFrom(BankAccount& b, int amount) {
    mu.Lock();
    b.withdrawImpl(amount);  // WARNING!  Calling withdrawImpl() requires locking b.mu.
    depositImpl(amount);     // OK.  depositImpl() has no requirements.
    mu.Unlock();
  }
};

上面的例子演示了安全分析背后基本概念。GUARDED_BY属性声明:线程必须先锁定mu,才能读写balance,由此保证增减操作是原子性的。类似地,EXCLUSIVE_LOCKS_REQUIRED属性声明:线程必须先锁定mu,才能调用withdrawImpl。因为调用者锁定了mu,在方法(method)内就可以安全地修改balance


depositImpl()方法没有EXCLUSIVE_LOCKS_REQUIRED属性,安全分析会报一个警告。因为安全分析不是过程间分析的(inter-procedural),所以调用要求必须显示声明。(意思是编译器不会联系上下文去分析这个方法需要加上什么属性,也不会自动帮你加上,所以需要显示地手动加上)transferFrom()里也会报一个警告,因为尽管锁定了this->mu,但并没有锁定b.mu,安全分析知道这是两个不同的互斥量。


withdraw()方法里也有一个警告,因为它没有解锁mu。每个锁定都必须有一个对应的解锁,安全分析会分析每对加锁/解锁。一个函数允许获取一个锁而不用释放这个锁(反过来也一样),但必须要标上属性,用LOCK/UNLOCK_FUNCTION

运行分析

使用-Wthread-safety标志

clang -c -Wthread-safety example.cpp

基本概念:能力(Capabilities)

线程安全分析使用能力,提供了一种保护资源的方法。这里所说的资源可以是数据成员,或者一个访问底层资源的方法/函数。安全分析确保了调用线程不能读写数据,不能调用函数,除非线程有能力。


能力与C++命名对象关联,命名对象声明了特定的方法来获取和释放能力。对象的名字用来识别这种能力。最常见的例子就是互斥量mutex。如果mu是互斥量,数据由mu保护,调用mu.Lock()会使得线程获取访问数据的能力。同样地,调用mu.Unlock()则释放这种访问数据的能力。


一个线程可能持有排它或共享这两种能力之一。排它能力一次只能由一个线程持有,而共享能力则可以由多个线程在同一时间持有。这是一种多读单写机制。读取受保护的数据只需要共享能力,写则要求排它。


在程序执行的过程中,线程持有一连串能力(比如锁定的所有互斥量),这些能力就像一串钥匙,线程拿着这些钥匙打开访问资源的大门。线程不能拷贝这些能力,也不能销毁它们,而只能从另一个线程那里获取这些能力,释放这些能力给另一个线程。标注并不知道这套获释能力的机制具体是怎么实现的,它假定了底层实现(互斥量的实现)以一种合适的方式转移能力。(意思是,标注不管实现,具体锁是怎么转移的,由Mutex实现,标注只管用就行了)


在程序运行过程中,线程持有的一连串能力是一个运行时概念。静态的安全分析计算出这些能力的一个大概值,叫做能力环境。在每个程序点,都会计算一下能力环境,并且描述那些静态持有或者不持有的能力。这个能力环境就构成了线程运行时的最小能力簇。(意思大概是,安全分析只能静态分析线程持有哪些mutex,分析不了动态持有的,所有这些静态分析得到的mutex就是线程运行时最少要获取的)

参考指南

线程安全分析使用属性来声明线程约束。属性必须附于命名的声明语句,例如类class、方法method和数据成员data member。强烈建议码农使用宏定义不同的属性,宏定义的例子能在mutex.h找到。下文都假定用宏定义。

GUARDED_BY(c) 和 PT_GUARDED_BY(c)

GUARDED_BY属性用在数据成员上,声明该数据成员受给定的能力保护。读该数据需要共享访问,写操作需要排它访问。

PT_GUARDED_BY属性作用类似,只不过是用在普通指针和智能指针。指针本身没有约束,指针指向的数据受能力保护。

Mutex mu;
int *p1            GUARDED_BY(mu); // p1受mu保护
int *p2            PT_GUARDED_BY(mu); // p2指向的数据受mu保护
unique_ptr<int> p3 PT_GUARDED_BY(mu); // p3指向的数据受mu保护

void test() {
  p1 = 0;             // Warning! p1本身的值受mu保护,需要锁定mu才行

  p2 = new int;       // OK.   p2随便可以更改指向,没问题
  *p2 = 42;           // Warning!  但修改指向的数据就不行

  p3.reset(new int);  // OK.   p3与p2一样
  *p3 = 42;           // Warning!
}

EXCLUSIVE_LOCKS_REQUIRED(...), SHARED_LOCKS_REQUIRED(...)

EXCLUSIVE_LOCKS_REQUIRED属性用在方法和函数上,表明调用线程必须拥有排它能力。可以指定多个能力。在进入函数前,必须拥有所有指定的排它能力,并且退出函数时依然持有这些能力。

SHARED_LOCKS_REQUIRED属性只要求共享访问能力。

Mutex mu1, mu2;
int a GUARDED_BY(mu1);
int b GUARDED_BY(mu2);

// 调用这个函数需要锁定mu1、mu2
void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) {
  a = 0;
  b = 0;
}

void test() {
  mu1.Lock();
  
  // 锁定了mu1, 但没锁定mu2,报警告
  foo();         // Warning!  Requires mu2.
  
  // 退出函数会依然持有mu1、mu2, 所以需要手动解锁
  mu1.Unlock();
}

EXCLUSIVE_LOCK_FUNCTION(...), SHARED_LOCK_FUNCTION(...), UNLOCK_FUNCTION(...)

EXCLUSIVE_LOCK_FUNCTION属性用在方法和函数上,表明函数体内获取能力,且不释放。调用者在进入函数前不能持有该能力,退出时会持有该能力。SHARED_LOCK_FUNCTION类似。

UNLOCK_FUNCTION属性表明函数释放能力。调用者在进入函数前必须持有能力,函数退出时不再持有该能力,不管能力是共享还是排它,调用都都不再持有。

Mutex mu;
MyClass myObject GUARDED_BY(mu);

// 属性表明: 调用前不能锁定mu,在函数体内锁定mu,函数退出不释放mu
// 满足属性
void lockAndInit() EXCLUSIVE_LOCK_FUNCTION(mu) {
  mu.Lock();
  myObject.init();
}

// 属性表明: 调用前已经锁定了mu, 函数退出应该释放能力
// 这里没有释放, 不满足属性, 报警
void cleanupAndUnlock() UNLOCK_FUNCTION(mu) {
  myObject.cleanup();
}  // Warning!  Need to unlock mu.

void test() {
  lockAndInit();
  myObject.doSomething();
  cleanupAndUnlock();
  
  // 假设前面都没有警告, 执行到这里, 线程已经释放了mu, 访问受mu保护的myObject会报警
  myObject.doSomething();  // Warning, mu is not locked.
}

如果没有往EXCLUSIVE_LOCK_FUNCTIONUNLOCK_FUNCTION传参数,那默认参数是this,此时安全分析不会检查函数体。这种场景适合类用抽象接口来隐藏锁的细节。

template <class T>
class LOCKABLE Container {
private:
  Mutex mu;
  T* data;

public:
  // Hide mu from public interface.
  void Lock() EXCLUSIVE_LOCK_FUNCTION() { mu.Lock(); }
  void Unlock() UNLOCK_FUNCTION() { mu.Unlock(); }

  T& getElem(int i) { return data[i]; }
};

void test() {
  Container<int> c;
  c.Lock();
  int i = c.getElem(0);
  c.Unlock();
}

LOCKS_EXCLUDED(...)

LOCKS_EXCLUDED属性用在方法和函数上,表明调用者在进入函数时,不能持有该能力。这个属性用来防止死锁。大部分互斥量都不是可重入的,所以如果函数再次尝试获取mu,则可能发生死锁。

Mutex mu;
int a GUARDED_BY(mu);

// 至于函数体内是怎么样的, 则不管
void clear() LOCKS_EXCLUDED(mu) {
  mu.Lock();
  a = 0;
  mu.Unlock();
}

void reset() {
  mu.Lock();
  
  // 调用函数前, 不能锁定mu,报警
  clear();     // Warning!  Caller cannot hold 'mu'.
  mu.Unlock();
}

不像LOCKS_REQUIRED,LOCKS_EXCLUDED是可选的。

NO_THREAD_SAFETY_ANALYSIS

NO_THREAD_SAFETY_ANALYSIS属性用在方法和函数上,用来关闭该函数的线程安全检查。如果线程安全函数或线程不安全函数逻辑太复杂,不想让这个函数执行安全分析,那就用这个属性。

class Counter {
  Mutex mu;
  int a GUARDED_BY(mu);

  void unsafeIncrement() NO_THREAD_SAFETY_ANALYSIS { a++; }
};

LOCK_RETURNED(c)

LOCK_RETURNED属性用在方法和函数上,用来返回一个能力的引用。

class MyClass {
private:
  Mutex mu;
  int a GUARDED_BY(mu);

public:
  Mutex* getMu() LOCK_RETURNED(mu) { return &mu; }

  // analysis knows that getMu() == mu
  void clear() EXCLUSIVE_LOCKS_REQUIRED(getMu()) { a = 0; }
};

ACQUIRED_BEFORE(...), ACQUIRED_AFTER(...)

ACQUIRED_BEFOREACQUIRED_AFTER属性用在成员声明上,特别是用在互斥量的声明上。属性规定了互斥量必须以特定的顺序获取,以防死锁。

Mutex m1;
Mutex m2 ACQUIRED_AFTER(m1);

// Alternative declaration
// Mutex m2;
// Mutex m1 ACQUIRED_BEFORE(m2);

void foo() {
  m2.Lock();
  // m1应该先于m2获取, 报警. 注意报警位置, 是报在m1, 没有报在m2.Lock()
  m1.Lock();  // Warning!  m2 must be acquired after m1.
  
  m1.Unlock();
  m2.Unlock();
}

LOCKABLE

LOCKABLE属性用在类上,表明该类可用作能力(也就是互斥量mutex的作用)。参考上面的Container例子,或者mutex.h里的Mutex class

SCOPED_LOCKABLE

SCOPED_LOCKABLE属性用在实现了RAII风格(Resource Acquire Is Initialization 获取资源即初始化)锁的类上,在这样的类实现里,能力在构造函数里获取,在析构函数里释放。这样的类需要特殊处理,因为构造函数和析构函数通过不同的名字引用能力。可参考mutex.h里的MutexLocker class

EXCLUSIVE_TRYLOCK_FUNCTION(, ...), SHARED_TRYLOCK_FUNCTION(, ...)

用在方法和函数上,表明方法函数尝试去获取能力,并返回一个bool值表示获取成功或失败。第一个参数必须是truefalse,用来指定获取成功的返回值,剩下的参数就跟(UN)LOCK_FUNCTION属性一样。使用例子参考mutex.h

ASSERT_EXCLUSIVE_LOCK(...) and ASSERT_SHARED_LOCK(...)

用在方法和函数上,用来做运行时测试,看调用线程是否持有能力。如果线程未持有能力,函数调用失败(无返回)。例子参考mutex.h

GUARDED_VAR and PT_GUARDED_VAR

弃用了,不用看。

警告标志(Warning flags)

  • -Wthread-safety`: 伞标,相当于打开了下面三个标志:
    • -Wthread-safety-attributes: 检查属性完整性;
    • -Wthread-safety-analysis: 执行安全分析;
    • -Wthread-safety-precise: 要求互斥量表达式精确匹配,如果代码里有很多别名,这个警告能被禁用;

当有新功能、新的检查被加到安全分析,也会有产生新的警告,这些一开始是作为beta警告,之后才会加入到正式版本的安全分析。

  • -Wthread-safety-beta: 新功能,默认是off;

FAQ

Q. 属性是放在头文件里好,还是.cc、.cpp、.cxx文件里好?

A. 属性呆在头文件里好。

Q. ”互斥量并不锁定在穿过这里的每条道路上(Mutex is not locked on every path through here)“ 这句话什么意思?

A. 参看No conditionally held locks.

安全分析的局限

词法范围

线程安全属性包含常见的c++表达式,因此遵循表达式的范围规则。也就是说,互斥量mutex必须先声明,再在属性上使用。在一个单独的类里,互斥量可以在声明前使用,因为属性是和方法体一起解析的,而c++会滞后解析方法体,直到类的定义结束,此时编译器其实掌握了类的所有信息了,自然就包括了互斥量的声明,这也没违背先声明再使用原则。在类之间,则不能声明前使用。

// 声明类, 类的定义在Bar的后面
class Foo;

// 编译器此时没有Foo的类详细信息, 不能访问其成员
class Bar {
  void bar(Foo* f) EXCLUSIVE_LOCKS_REQUIRED(f->mu);  // Error: mu undeclared.
};

class Foo {
  Mutex mu;
};

私有互斥量

好的软件工程实践要求互斥量应该是私有成员,因为线程安全类使用的锁机制是这个类的内部实现,不应该公开。然而,私有的互斥量有时候会通过公开接口泄露。线程安全属性遵循c++访问限制,如果mu是对象c的一个私有成员,那么在属性上写c.mu会报错。

一个迂回办法是:用LOCK_RETURNED属性起一个公开的方法,实际不暴露底层的互斥量。

class MyClass {
private:
  Mutex mu;

public:
  // For thread safety analysis only.  Does not actually return mu.
  Mutex* getMu() LOCK_RETURNED(mu) { return 0; }

  void doSomething() EXCLUSIVE_LOCKS_REQUIRED(mu);
};

void doSomethingTwice(MyClass& c) EXCLUSIVE_LOCKS_REQUIRED(c.getMu()) {
  // The analysis thinks that c.getMu() == c.mu
  c.doSomething();
  c.doSomething();
}

调用doSomethingTwice()时,要求锁定c.mu,但不能直接写c.mu,因为c.mu是私有的。不鼓励用这样的方法,因为这违背了封装原则,但有时候又是必要的,特别是在现有的代码库里加标注时。总之,这个迂回方法就是定义一个getMu()作为一个伪getter方法,仅仅是用来做线程安全分析。(译者注:仅仅是用了让安全分析通过,实际代码运行不会去锁定c.mu

引用传递假阴(False negatives on pass by reference)

当前版本的安全分析只检查通过名称直接访问数据成员的操作,如果通过指针、引用间接访问数据成员,将不会有警告产生。因此下面的代码将不会产生警告:

Mutex mu;
int a GUARDED_BY(mu);

void clear(int& ra) { ra = 0; }

void test() {
  int *p = &a;
  *p = 0;       // No warning.  *p is an alias to a.

  clear(a);     // No warning.  'a' is passed by reference.
}

在当前版本,这是导致假阴最大的原因。根本原因是:标注是附属在数据成员上,而不是在类型上。&a的类型应该是int GUARDED_BY(mu)*,而不是int*,也因此语句p = &a应该报错。然而,标注属性如果附属在类型上,那么将会侵入c++类型系统,造成大改,后果就是影响模板实例化、函数重载等。对于这个问题的完整解决办法暂时还没有。

未来版本的安全分析会为指针和别名提供更好的支持,同时对受保卫的数据成员的类型进行有限的检查,减少假阴次数。

条件分支下不持有锁(No conditionally held locks)

在每个程序点,安全分析必须能够判断一个锁是否被持有着。下面这段代码中,锁可能被持有,因此将产生一个虚假警告:(译者注:时刻注意,安全分析是静态分析,此时它判断不了b的值,也就不知道mu是否被持有,与安全分析规定相违背,所以会有警告。警告是虚假的,意思是这实际上不是问题)

void foo() {
  bool b = needsToLock();
  if (b) mu.Lock();
  ...  // Warning!  Mutex 'mu' is not held on every path through here.
  if (b) mu.Unlock();
}

不检查构造析构

安全分析不对构造、析构函数内部做检查,换句话说,这两个函数就像是标注了NO_THREAD_SAFETY_ANALYSIS一样。之所以不检查,是因为在初始化期间,只有一个线程有正在初始化对象的访问权限,因此不用获取锁就初始化受保卫的成员,这是安全的做法(也是惯例做法),析构函数也是一样。

理想情况下,安全分析允许在初始化、要销毁的对象中进行对受保卫成员的初始化,而仍然要求对其他的一切要有访问限制。然而实际上很难限制,因为在复杂的基于指针的数据结构中,很难决定这个封装的对象拥有什么样的数据。

不内联

跟类型检查一样,安全分析是严格地逐过程分析的。它只依赖在函数上声明的属性,不会步渐式的分析,也不会内联函数调用。(译者注:静态过程分析,不会像调用函数那样动态地分析)

template<class T>
class AutoCleanup {
  T* object;
  void (T::*mp)();

public:
  AutoCleanup(T* obj, void (T::*imp)()) : object(obj), mp(imp) { }
  ~AutoCleanup() { (object->*mp)(); }
};

Mutex mu;
void foo() {
  mu.Lock();
  AutoCleanup<Mutex>(&mu, &Mutex::Unlock);
  ...
}  // Warning, mu is not unlocked.

在上面这个例子,Autocleanup的析构函数会调用mu.Unlock()释放mu,所以报的那个警告其实是不对的。然而,安全分析不会内联展开析构函数,自然看不到Unlock()操作,而且它也没办法标注构造函数,因为析构函数调用了一个不能从静态分析得来的未知函数。

LOCKS_EXCLUDED不传递

函数调用标注了LOCKS_EXCLUDED的函数,并不要求在调用函数上也标注LOCKS_EXCLUDED,这将导致假阴。在这点上,LOCKS_EXCLUDED就跟LOCKS_REQUIRED不同,

class Foo {
  Mutex mu;

  void foo() {
    mu.Lock();
    // 按理说这里应该报警
    bar();                // No warning
    mu.Unlock();
  }

  void bar() { baz(); }   // No warning.  (Should have LOCKS_EXCLUDED(mu).)

  void baz() LOCKS_EXCLUDED(mu);
};

之所以LOCKS_EXCLUDED不传递,是因为它很容易破坏封装。要求函数列出恰好在内部获取的私有锁的名称是一个坏主意。(译者注:刚才那个例子,要想让foo函数正常报警,就要像baz()一样,这样写:

void foo() LOCKS_EXCLUDED(mu); mufoo函数自己内部获取,而这其实是强制码农必须清楚foo函数的逻辑,破坏了封装。所以安全分析就没做传递)

不检查别名

安全分析目前不检查指针别名。因此如果两个指针指向同一个互斥量,将会假阳。

class MutexUnlocker {
  Mutex* mu;

public:
  // 调用者在进入函数前必须持有能力,函数退出时不再持有该能力
  MutexUnlocker(Mutex* m) UNLOCK_FUNCTION(m) : mu(m)  { mu->Unlock(); }
  
  // 函数体内获取能力,且不释放。调用者在进入函数前不能持有该能力,退出时会持有该能力
  ~MutexUnlocker() EXCLUSIVE_LOCK_FUNCTION(mu) { mu->Lock(); }
};

Mutex mutex;
// 在进入函数前,必须拥有所有指定的排它能力,并且退出函数时依然持有这些能力。
void test() EXCLUSIVE_LOCKS_REQUIRED(mutex) {
  {
    MutexUnlocker munl(&mutex);  // unlocks mutex
    doSomeIO();
  }                              // Warning: locks munl.mu
}

MutexUnlocker理应是是mutex.hMutexLocker是二元类。然而却并不是,因为安全分析并不知道mun1.mu == mutexSCOPED_LOCKABLE属性是处理别名的。

(译者注:把自己想像成安全分析,去静态分析那段代码,进入test()前,已经锁定了mutex,初始化对象MutexUnlocker munl(&mutex); 调用构造函数,因为构造函数是这样写的:MutexUnlocker(Mutex* m) UNLOCK_FUNCTION(m) : mu(m) { mu->Unlock(); } 此时 mmu两个指针指向了同一个互斥量了,调用其中一个进行解锁:mu->Unlock()。到现在这句为止没报警,说明安全分析对指针别名的Unlock()是可以识别的,一个指针Unlock()了,可以识别到另一个指针也Unlock()了。之后test()函数结束时,析构MutexUnlocker():调用mu->Lock()mun1对象成为垃圾。之后报警。报警的原因我猜是对象销毁,理应释放掉所有的互斥量,但mun1没有释放,才报的警。而其实,这个报警是没必要的,因为调用完test(),线程还要继续持有mutex。因为安全分析并不知道mun1.mu是传入的mutex,所以在销毁mun1时,机械地认为mun1.mu理应一起销毁,才导致报一个不该报的警告,即假阳)

ACQUIRED_BEFORE(...) and ACQUIRED_AFTER(...)

目前暂未实现,后续的版本更新

mutex.h

线程安全分析能与任何的线程库一起使用,但它要求线程API包裹在标注了相应的属性的类和方法里。mutex.h是一个例子,作为底层实现的一个办法。

#ifndef THREAD_SAFETY_ANALYSIS_MUTEX_H
#define THREAD_SAFETY_ANALYSIS_MUTEX_H

// Enable thread safety attributes only with clang.
// The attributes can be safely erased when compiling with other compilers.
#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x)   __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x)   // no-op
#endif

#define THREAD_ANNOTATION_ATTRIBUTE__(x)   __attribute__((x))

#define GUARDED_BY(x) \
  THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))

#define GUARDED_VAR \
  THREAD_ANNOTATION_ATTRIBUTE__(guarded)

#define PT_GUARDED_BY(x) \
  THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))

#define PT_GUARDED_VAR \
  THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded)

#define ACQUIRED_AFTER(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))

#define ACQUIRED_BEFORE(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))

#define EXCLUSIVE_LOCKS_REQUIRED(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))

#define SHARED_LOCKS_REQUIRED(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))

#define LOCKS_EXCLUDED(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))

#define LOCK_RETURNED(x) \
  THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))

#define LOCKABLE \
  THREAD_ANNOTATION_ATTRIBUTE__(lockable)

#define SCOPED_LOCKABLE \
  THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)

#define EXCLUSIVE_LOCK_FUNCTION(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))

#define SHARED_LOCK_FUNCTION(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))

#define ASSERT_EXCLUSIVE_LOCK(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__))

#define ASSERT_SHARED_LOCK(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__))

#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))

#define SHARED_TRYLOCK_FUNCTION(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))

#define UNLOCK_FUNCTION(...) \
  THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))

#define NO_THREAD_SAFETY_ANALYSIS \
  THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)


// Defines an annotated interface for mutexes.
// These methods can be implemented to use any internal mutex implementation.
class LOCKABLE Mutex {
public:
  // Acquire/lock this mutex exclusively.  Only one thread can have exclusive
  // access at any one time.  Write operations to guarded data require an
  // exclusive lock.
  void Lock() EXCLUSIVE_LOCK_FUNCTION();

  // Acquire/lock this mutex for read operations, which require only a shared
  // lock.  This assumes a multiple-reader, single writer semantics.  Multiple
  // threads may acquire the mutex simultaneously as readers, but a writer must
  // wait for all of them to release the mutex before it can acquire it
  // exclusively.
  void ReaderLock() SHARED_LOCK_FUNCTION();

  // Release/unlock the mutex, regardless of whether it is exclusive or shared.
  void Unlock() UNLOCK_FUNCTION();

  // Try to acquire the mutex.  Returns true on success, and false on failure.
  bool TryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true);

  // Try to acquire the mutex for read operations.
  bool ReaderTryLock() SHARED_TRYLOCK_FUNCTION(true);

  // Assert that this mutex is currently held by the calling thread.
  void AssertHeld() ASSERT_EXCLUSIVE_LOCK();

  // Assert that is mutex is currently held for read operations.
  void AssertReaderHeld() ASSERT_SHARED_LOCK();
};


// MutexLocker is an RAII class that acquires a mutex in its constructor, and
// releases it in its destructor.
class SCOPED_LOCKABLE MutexLocker {
private:
  Mutex* mut;

public:
  MutexLocker(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu) : mut(mu) {
    mu->Lock();
  }
  ~MutexLocker() UNLOCK_FUNCTION() {
    mut->Unlock();
  }
};

#endif  // THREAD_SAFETY_ANALYSIS_MUTEX_H

posted on 2023-03-04 16:44  留校察看  阅读(330)  评论(0编辑  收藏  举报

导航