GUARDED_BY —— clang的线程安全分析模块 thread safety analysis

通过代码注解(annotations )告诉编译器哪些成员变量和成员函数是受哪个 mutex 保护,这样如果忘记了加锁,编译器会给警告。因为在后续维护别人的代码时候,往往不像原作者那样深刻理解设计意图,特别容易遗漏线程安全的假设。

Thread Safety Annotations 可以方便阅读代码,而且在编译的时候会检查锁的状态。

其工作原理非常类似于多线程程序的类型系统。除了声明数据类型之外,还可以声明在多线程环境中如何控制对该数据的访问。比如如果foo遍历被互斥锁mu保护,那么只要有一段代码在没有先锁定mu的情况下读取或写入foo,则analysis会报awrning。同样,如果有仅应由GUI线程调用的特定例程,则分析将在其他线程调用这些例程时发出警告。

例子

GUARDED_BY是指当线程需要返回balance的时候,需要加锁。
EXCLUSIVE_LOCKS_REQUIRE表示调用线程在调用withdrawImpl之前必须先锁住mu。
depositImpl没有EXCLUSIVE_LOCKS_REQUIRED,所以analysis会有一个warning,

#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_REQUIRE(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

表示变量被某个锁保护。

定义

//art/runtime/base/macros.h
#if defined(__SUPPORT_TS_ANNOTATION__) || defined(__clang__)
#define THREAD_ANNOTATION_ATTRIBUTE__(x)   __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x)   // no-op
#endif

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

必须定义了__SUPPORT_TS_ANNOTATION__或者__clang__ Thread Safety Annotations 才起作用,否则是no-op。

PT_GUARDED_BY在指针上提供类似功能,保护指针指向的变量。

Mutex mu;
int *p1            GUARDED_BY(mu);
int *p2            PT_GUARDED_BY(mu);
unique_ptr<int> p3 PT_GUARDED_BY(mu);

void test() {
  p1 = 0;             // Warning!

  p2 = new int;       // OK.
  *p2 = 42;           // Warning!

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

EXCLUSIVE_LOCKS_REQUIRED

表示调用线程必须在函数入口之前持有锁,直到函数结束。

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

void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) {
  a = 0;
  b = 0;
}

void test() {
  mu1.Lock();
  foo();         // Warning!  Requires mu2.
  mu1.Unlock();
}

EXCLUSIVE_LOCK_FUNCTION

EXCLUSIVE_LOCK_FUNCTION表示函数持有锁,不释放,caller在函数入口处不能持有。
UNLOCK_FUNCTION表示函数释放锁。

Mutex mu;
MyClass myObject GUARDED_BY(mu);

void lockAndInit() EXCLUSIVE_LOCK_FUNCTION(mu) {
  mu.Lock();
  myObject.init();
}

void cleanupAndUnlock() UNLOCK_FUNCTION(mu) {
  myObject.cleanup();
}  // Warning!  Need to unlock mu.

void test() {
  lockAndInit();
  myObject.doSomething();
  cleanupAndUnlock();
  myObject.doSomething();  // Warning, mu is not locked.
}

如果没有参数传给(UN)LOCK_FUNCTION,则this隐含被传入,analysis不会检查函数体。这种方式是给通过抽象接口隐藏locking details的类使用的。

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

表示bar函数被调用的时候,本线程没有占有Obj.Mu锁,否则会出错。
该方法用于防止死锁。

Mutex mu;
int a GUARDED_BY(mu);

void clear() LOCKS_EXCLUDED(mu) {
  mu.Lock();
  a = 0;
  mu.Unlock();
}

void reset() {
  mu.Lock();
  clear();     // Warning!  Caller cannot hold 'mu'.
  mu.Unlock();
}

NO_THREAD_SAFETY_ANALYSIS

关闭thread safety checking。用于给线程不安全的函数使用,或者是线程安全但是对于analysis来说太复杂。

LOCK_RETURNED

该函数返回锁的引用,用于标识getter函数。

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(...)

变量的属性,声明mutex或其他锁。声明了获取锁的强制顺序。

Mutex m1;
Mutex m2 ACQUIRED_AFTER(m1);

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

void foo() {
  m2.Lock();
  m1.Lock();  // Warning!  m2 must be acquired after m1.
  m1.Unlock();
  m2.Unlock();
}

LOCKABLE

类可被用于capability。

SCOPED_LOCKABLE

实现RAII风格的锁的类,在构造函数中创建锁,在析构函数中释放锁。

EXCLUSIVE_TRYLOCK_FUNCTION, SHARED_TRYLOCK_FUNCTION

尝试获取锁,成功返回true,失败返回false。

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

在线测试calling thread是否持有given capability。

已知的限制

作用范围

类内mutex可以在任意处定义,但是类间mutex必须在使用前被声明。

class Foo;

class Bar {
  void bar(Foo* f) EXCLUSIVE_LOCKS_REQUIRED(f->mu);  // Error: mu undeclared.
};

class Foo {
  Mutex mu;
};

传引用会引起假阴性

现在guarded只支持直接用name,如果是用过指针或引用,则不会产生warning。因为注解被附加在数据成员上,而非type上。&a的类型是int GUARDED_BY(mu)* 而非int*,所以p=&a会产生一个type error。

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.
}

条件锁

一些“锁可能被持有”的代码片段可能会导致虚假warning(假阳性)。

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

不检查构造函数和析构函数

因为当初始化或析构的时候,只有一个线程能访问。

无内联

线程安全analysis依赖于函数内声明的attributes,不会尝试step inside,或内联任何方法,因此下面的语句会有问题:

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.

别名不能analysis

指针别名不能被track,所以下面的代码会产生假阳性,如果两个指针指向同一个mutex:MutexUnlocker在mutex.h中是MutexLocker的对偶。因为analysis不知道mul.mu就是mutex。可以用SCOPED_LOCKABLE处理别名。

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
}

参考:http://releases.llvm.org/3.5.0/tools/clang/docs/ThreadSafetyAnalysis.html

comments powered by Disqus