本文内容来自 Thread Safety Analysis,如需完整学习,请参考相关链接。
Clang线程安全分析工具是C++语言的一种扩展,用于警告代码中潜在的竞争条件。它在编译期间进行静态分析,无运行期性能损耗。即使该工具仍处在开发阶段,但已足够成熟,适合部署在生产环境上。
它的工作原理类似于一个针对多线程编程的类型系统。例如,变量 foo
可被多线程访问,当分析工具检测到该变量在读写时没有被对应的锁所保护时,会提示一个警告。
基本概念
clang的线程安全分析工具使用capabilities
来保护资源。这里的资源指的是成员数据,或者是提供访问底层资源的函数或方法。它能确保调用线程只有先拥有capability
,才能访问对应资源。
假如 mu
代表一个 mutex
,当线程执行 mu.Lock()
后,代表它获得了访问 mu
所保护资源的 capability
,当它调用 mu.unLock()
后,代表它释放了该 capability
。该线程拥有的capability
是不可拷贝的,也不能销毁它,它只能释放该capability
,以便其他线程获得该capability
。
使用方法
线程安全分析工具使用属性注解来声明依赖,这些属性注解是附加在类、方法、数据成员之上的。官方推荐使用宏(mutex.h)来使属性注解可读性更好、可理解。
运行方法很简单,执行
clang -c -Wthread-safety example.cpp
适用于变量的注解
GUARDED_BY: 该属性声明,线程在对该变量进行读写之前,一定要获得对应的锁,以确保对该变量的操作是线程安全的。
PT_GUARDED_BY: 和GUARDED_BY类似,它用于指针和智能指针上,对指针自身没有约束,但对它所指向的数据施加属性保护。
Mutex mu; int *p1 GUARDED_BY(mu); int *p2 PT_GUARDED_BY(mu); unique_ptr
p3 PT_GUARDED_BY(mu); void test() { p1 = 0; // Warning!
*p2 = 42; // Warning! p2 = new int; // OK.
*p3 = 42; // Warning! p3.reset(new int); // OK. }
适用于函数的注解
REQUIRES(mu) 指示调用线程在调用该函数前,必须先获得mu锁。它假设调用者在调用该函数前,已经拥有mu锁,内部对共享变量的修改无需额外加锁。
REQUIRES_SHARED(), 与 REQUIRES 类似,但仅要求共享读访问权限。
Mutex mu1, mu2; int a GUARDED_BY(mu1); int b GUARDED_BY(mu2);
void foo() REQUIRES(mu1, mu2) { a = 0; b = 0; }
void test() { mu1.Lock(); foo(); // Warning! Requires mu2. mu1.Unlock(); }
ACQUIRE() 声明要求函数内部获得锁,直到退出该函数也无需释放它。调用者在调用该函数前,不能持有该锁。
RELEASE() 声明函数具备释放锁的能力,调用者在调用入口处无需持有该锁,函数退出前会主动释放该锁。
Mutex mu; MyClass myObject GUARDED_BY(mu);
void lockAndInit() ACQUIRE(mu) { mu.Lock(); myObject.init(); }
void cleanupAndUnlock() RELEASE(mu) { myObject.cleanup(); } // Warning! Need to unlock mu.
void test() { lockAndInit(); myObject.doSomething(); cleanupAndUnlock(); myObject.doSomething(); // Warning, mu is not locked. }
如果没有参数传递给 ACQUIRE 或 RELEASE,则假定入参为 this ,分析工具不会检查函数内部实现。常用在隐藏抽象接口的内部锁的实现细节。
EXCLUDES(...) 声明调用者不能持有给定
capabilities
, 该注解用来预防死锁,对于不可重入的锁,当同一个函数重入时,会获得2次锁,造成死锁。Mutex mu; int a GUARDED_BY(mu);
void clear() EXCLUDES(mu) { mu.Lock(); a = 0; mu.Unlock(); }
void reset() { mu.Lock(); clear(); // Warning! Caller cannot hold 'mu'. mu.Unlock(); }
NO_THREAD_SAFETY_ANALYSIS: 关闭线程安全检查,该属性不属于声明的一部分,使用时要放在 .cpp 文件中。
适用于类的注解
CAPABILITY(
): 指明该类的实例可被用作 capaility
,string参数用来表明该capaility
的种类,在警告时会输出。SCOPED_CAPABILITY:指明该类用于RAII风格的资源管理。