线程安全注解——GUARDED_BY

线程安全注解是现代C++开发的机制,可以在代码编译阶段检查锁的使用情况,从而发现线程同步的相关问题。

1. 线程安全注解的使用

按照clang的线程安全注解文档 可以看到如果需要使用线程安全注解的相关功能需要对锁进行相关的封装之后才可以进行使用。目前存在clang存在两套线程安全注解的使用方法,本文仅介绍最新的线程安全注解使用方式(新的capability机制已经完全取代了之前的lockable机制)。
在使用前,首先了解一下clang官方推荐的mutex.h中实现的几个宏

CAPABILITY//该宏负责指定相关的类使用线程安全检测机制,即标准文档所说的功能,用于直接修饰类

SCOPED_CAPABILITY//负责实现RAII样式锁定的类属性,即在构造时获取能力,析构时释放能力。其他和CAPABILITY类似。

GUARDED_BY //声明数据成员受给定功能保护。对数据的读取操作需要共享访问,而写入操作需要独占访问。

PT_GUARDED_BY和GUARDED_BY类似,用于指针和智能指针,用户保护指针指向的数据。

ACQUIRED_BEFORE需要在另一个能力获取之前被调用

ACQUIRED_AFTER需要在另一个能力获取之后被调用

REQUIRES用来修饰函数,使其调用线程必须具有对给定功能的独占访问权限。被修饰的函数在进入前必须已经持有能力,函数退出时不在持有能力。
这个变量的概念比较绕,实际上转化为代码之后,比较好理解,下面时clang提供的官方代码

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();
}

REQUIRES_SHARED//和REQUIRES类似,只不过REQUIRES_SHARED可以共享地获取能力

ACQUIRE//用来修饰函数,使其调用线程必须具有对给定功能的独占访问权限。被修饰的函数在进入前必须持有能力。

ACQUIRE_SHARED//和ACQUIRE相同,只是能力可以共享
****//用来修饰函数,使其调用线程必须具有对给定功能的独占访问权限。被修饰的函数退出时不在持有能力。
RELEASE_SHARED//和RELEASE相同,用于修饰释放可以共享的能力
RELEASE_GENERIC//和RELEASE相同,用于修饰释放共享的能力和非共享的能力
TRY_ACQUIRE//尝试获取能力
TRY_ACQUIRE_SHARED//尝试获取共享的能力
EXCLUDES//修饰函数一定不能具有某项能力
ASSERT_CAPABILITY//修饰调用线程已经具有给定的能力。
RETURN_CAPABILITY//修饰函数负责返回给定的能力
NO_THREAD_SAFETY_ANALYSIS//修饰函数,关闭能力检查

下面是一个简单的例子:

头文件
#ifndef __PTHREAD_H__
#define __PTHREAD_H__

//=======================
//  Includes
//=======================

#include 
#include 

// 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 CAPABILITY(x) \
    THREAD_ANNOTATION_ATTRIBUTE__(capability(x))

#define SCOPED_CAPABILITY \
    THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)

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

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

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

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

#define REQUIRES(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))

#define REQUIRES_SHARED(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))

#define ACQUIRE(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))

#define ACQUIRE_SHARED(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))

#define RELEASE(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))

#define RELEASE_SHARED(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))

#define RELEASE_GENERIC(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__))

#define TRY_ACQUIRE(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))

#define TRY_ACQUIRE_SHARED(...) \
    THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))

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

#define ASSERT_CAPABILITY(x) \
    THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))

#define ASSERT_SHARED_CAPABILITY(x) \
    THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))

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

#define NO_THREAD_SAFETY_ANALYSIS \
    THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)

//=======================
//  Classes
//=======================

class CAPABILITY("mutex") PMutex
{
public:
    PMutex();

    virtual ~PMutex();

    bool lock() ACQUIRE();

    bool trylock() TRY_ACQUIRE(true);

    void unlock() RELEASE();

    pthread_mutex_t *getMutex();

private:
    PMutex(const PMutex &m);

    PMutex &operator=(const PMutex &m);

    pthread_mutex_t mutex;
};

#endif
源文件如下:
#include "PThread.h"
PMutex::PMutex()
{
    pthread_mutexattr_t attr;
    int rc;

    rc = pthread_mutexattr_init(&attr);
    assert(rc == 0);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    rc = pthread_mutex_init(&mutex, &attr);
    assert(rc == 0);
    pthread_mutexattr_destroy(&attr);
}

PMutex::~PMutex()
{
    pthread_mutex_destroy(&mutex);
}

bool PMutex::lock()
{
    int rc = pthread_mutex_lock(&mutex);

    assert(rc == 0);
    return rc == 0;
}

bool PMutex::trylock()
{
    return 0 == pthread_mutex_trylock(&mutex);
}

void PMutex::unlock()
{
    int rc = pthread_mutex_unlock(&mutex);

    assert(rc == 0);
    (void)rc;
}

pthread_mutex_t* PMutex::getMutex()
{
    return &mutex;
}

这样就完成了对与线程安全注解的基本封装。

下面是使用的main.cc

#include 
#include "PThread.h"
using namespace std;
class A
{
public:
    A()
    {
        a = 0;
    }
    ~A() {}
    PMutex aMutex;
    int a GUARDED_BY(aMutex);
    void oprA()
    {
        a = 3;
    }
};
int main()
{
    A *usea = new A;
    usea->oprA();
    cout << "test now" << endl;
    return 0;
}

然后使用clang进行编译,编译命令如下:

clang++ -o test main.cc  -c -Wthread-safety

其中 -Wthread-safety 负责打开编译器的线程安全注解的检查。

进行编译时,会出现如下警告

main.cc:66:9: warning: writing variable ‘a’ requires holding mutex ‘aMutex’ exclusively [-Wthread-safety-analysis]
a = 3;
^
1 warning generated.

可以看到编译器发现了a的赋值操作之前没有进行锁保护。

你可能感兴趣的:(C++,c++,多线程,编程语言)