clang ThreadSafetyAnalysis 线程安全分析

本文介绍了Clang的线程安全分析功能,用于检测C++代码中的竞态条件。分析是静态的,无运行时开销,由Google开发并已应用于生产环境。文章涵盖了分析的基本概念,如假阳性和假阴性,以及如何通过属性声明线程约束。通过示例展示了如何使用GUARDED_BY、EXCLUSIVE_LOCKS_REQUIRED等属性确保线程安全,以及如何通过-Wthread-safety标志运行分析。
摘要由CSDN通过智能技术生成

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dituirenwu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值