宏的意義
-
一些編譯器宏的使用方法(clang 的線程安全注解 —— Thread Safety Annotation):
修飾類的宏
//CAPABILITY 表明某個(gè)類對(duì)象可以當(dāng)作 capability 使用,其中 x 的類型是 string,能夠在錯(cuò)誤信息當(dāng)中指出對(duì)應(yīng)的 capability 的名稱 #define CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) //SCOPED_CAPABILITY 用于修飾基于 RAII 實(shí)現(xiàn)的 capability。 #define SCOPED_CAPABILITY \ THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)注:capability 是 TSA 中的一個(gè)概念,用來為資源的訪問提供相應(yīng)的保護(hù)。這里的資源可以是數(shù)據(jù)成員,也可以是訪問某些潛在資源的函數(shù)/方法。capability 通常表現(xiàn)為一個(gè)帶有能夠獲得或釋放某些資源的方法的對(duì)象,最常見的就是 mutex 互斥鎖。換言之,一個(gè) mutex 對(duì)象就是一個(gè) capability
修飾數(shù)據(jù)成員的宏:
//*GUARD_BY 用于修飾對(duì)象,表明該對(duì)象需要受到 capability 的保護(hù) #define GUARDED_BY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) //PT_GUARDED_BY(mutex) 用于修飾指針類型變量,在更改指針變量所指向的內(nèi)容前需要加鎖,否則發(fā)出警告 #define PT_GUARDED_BY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) //示例用法 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 = 42; // Warning! p2 = new int; // OK. *p3 = 42; // Warning! p3.reset(new int); // OK. }修飾 capability 的宏
//ACQUIRED_BEFORE 和 ACQUIRED_AFTER 主要用于修飾 capability 的獲取順序,用于避免死鎖 #define ACQUIRED_BEFORE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) #define ACQUIRED_AFTER(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) //示例用法 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(); }修飾函數(shù)/方法(成員函數(shù))的宏:
//REQUIRES 聲明調(diào)用線程必須擁有對(duì)指定的 capability 具有獨(dú)占訪問權(quán)。可以指定多個(gè) capabilities。函數(shù)/方法在訪問資源時(shí),必須先上鎖,再調(diào)用函數(shù),然后再解鎖(注意,不是在函數(shù)內(nèi)解鎖) #define REQUIRES(...) \ THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) // REQUIRES_SHARED 功能與 REQUIRES 相同,但是可以共享訪問 #define REQUIRES_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) //示范用法 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 表示一個(gè)函數(shù)/方法需要持有一個(gè) capability,但并不釋放這個(gè) capability。調(diào)用者在調(diào)用被 ACQUIRE 修飾的函數(shù)/方法時(shí),要確保沒有持有任何 capability,同時(shí)在函數(shù)/方法結(jié)束時(shí)會(huì)持有一個(gè) capability(加鎖的過程發(fā)生在函數(shù)體內(nèi)) #define ACQUIRE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) //ACQUIRE_SHARED 與 ACQUIRE 的功能是類似的,但持有的是共享的 capability #define ACQUIRE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) //示范用法 MutexLock mu; class MyClass{ public: void doSomething(){ cout << "x = " << x << endl; } MyClass() = default; void init(const int a){ x = a; } void cleanup(){ x = 0; } private: int x; }; MyClass myObject GUARDED_BY(mu); void lockAndInit(MyClass& myObject) ACQUIRE(mu) { mu.lock(); myObject.init(10); } void cleanupAndUnlock(MyClass& myObject) RELEASE(mu) { myObject.cleanup(); } // Warning! Need to unlock mu. void test() { MyClass myObject; //局部對(duì)象掩蓋了全局的 myObject 對(duì)象,而全局的 myObject 對(duì)象受到了 mu 的保護(hù) lockAndInit(myObject); myObject.doSomething(); cleanupAndUnlock(myObject); myObject.doSomething(); } int main(void){ test(); myObject.doSomething(); // Warning! MyObject is guarded by mu return 0; } //ACQUIRE 和 ACQUIRE_SHARED 的嘗試版本,第一個(gè)參數(shù)是 bool,true 代表成功,false 代表失敗 #define TRY_ACQUIRE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) #define TRY_ACQUIRE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))//RELEASE 和 RELEASE_SHARED 與 ACQUIRE 和 ACQUIRE_SHARED 正相反,它們表示調(diào)用方在調(diào)用該函數(shù)/方法時(shí)需要先持有鎖,而當(dāng)函數(shù)執(zhí)行結(jié)束后會(huì)釋放鎖(釋放鎖的行為發(fā)生在函數(shù)體內(nèi)) #define RELEASE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) #define RELEASE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) //實(shí)例用法 template <class T> class CAPABILITY("mutex") Container { private: Mutex mu; T* data; public: // Hide mu from public interface. void Lock() ACQUIRE() { mu.Lock(); } void Unlock() RELEASE() { mu.Unlock(); } T& getElem(int i) { return data[i]; } }; void test() { Container<int> c; c.Lock(); int i = c.getElem(0); c.Unlock(); }//EXCLUDES 用于顯式聲明函數(shù)/方法不應(yīng)該持有某個(gè)特定的 capability。由于 mutex 的實(shí)現(xiàn)通常是不可重入的,因此 EXCLUDES 通常被用來預(yù)防死鎖 #define EXCLUDES(...) \ THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) //實(shí)例用法 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(); }//ASSERT_* 表示在運(yùn)行時(shí)檢測調(diào)用線程是否持有 capability #define ASSERT_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) #define ASSERT_SHARED_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))//NO_THREAD_SAFETY_ANALYSIS 表示關(guān)閉某個(gè)函數(shù)/方法的 TSA 檢測,通常只用于兩種情況:1,該函數(shù)/方法可以被做成非線程安全;2、函數(shù)/方法太過復(fù)雜,TSA 無法進(jìn)行檢測 #define NO_THREAD_SAFETY_ANALYSIS \ THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)__ //示范用法(NO_THREAD_SAFETY_ANALYSIS 并不是函數(shù)接口的一部分,故相比于放在頭文件中,放在 cc 文件中更為恰當(dāng)) class Counter { Mutex mu; int a GUARDED_BY(mu); void unsafeIncrement() NO_THREAD_SAFETY_ANALYSIS { a++; } }; //RETURN_CAPABILITY 通常用于修飾那些被當(dāng)作 capability getter 的函數(shù),這些函數(shù)會(huì)返回 capability 的引用或指針 #define RETURN_CAPABILITY(x) \ THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))#define MCHECK(ret) ({ __typeof__ (ret) errnum = (ret); \ assert(errnum == 0); (void) errnum;}) class CAPABILITY("mutex") MutexLock : noncopyable{ public: MutexLock() : holder_(0) { MCHECK(pthread_mutex_init(&mutex_, NULL)); } ... };注:
- clang 的用法和 g++ 比較類似,要測試上面代碼可以使用命令:clang -c -Wthread-safety example.cpp
clang 的編譯選項(xiàng)
- -Wthread-safety 打開線程安全注解
- -Wthread-safety-negative
Negative Capability
線程安全注解(TSA)的引入旨在預(yù)防競態(tài)條件和死鎖的發(fā)生。GUARDED_BY 和 REQUIRES 通過調(diào)用者確保在讀/寫數(shù)據(jù)之前取得互斥鎖,從而避免競態(tài)的發(fā)生,而 EXCLUDES 則通過保證某個(gè)調(diào)用者不持有鎖來避免死鎖的發(fā)生。
但是,EXCLUDES 通常只是可選選項(xiàng),它并不能保證得到和 REQUIRES 同樣級(jí)別的安全性,特別是在以下兩種情況下:
- 一個(gè)函數(shù)可以持有其他非 exclude 的 capability
- 一個(gè) exclude 的函數(shù) f1 調(diào)用了另一個(gè)非 exclude 的函數(shù) f2,而 f2 持有 f1 所 exclude 的 capability。換句話講,exclude 屬性不能在多個(gè)函數(shù)之間傳遞
針對(duì)第二種情況,有一個(gè)例子:
class Foo {
Mutex mu;
void foo() {
mu.Lock();
bar(); // No warning.
baz(); // No warning.
mu.Unlock();
}
void bar() { // No warning. (Should have EXCLUDES(mu)).
mu.Lock();
// ...
mu.Unlock();
}
void baz() {
bif(); // No warning. (Should have EXCLUDES(mu)).
}
void bif() EXCLUDES(mu);
};
通過 REQUIRES( !mu) 來代替 EXCLUDES 則可以避免這種情況:
class FooNeg {
Mutex mu;
void foo() REQUIRES(!mu) { // foo() now requires !mu.
mu.Lock();
bar();
baz();
mu.Unlock();
}
void bar() {
mu.Lock(); // WARNING! Missing REQUIRES(!mu).
// ...
mu.Unlock();
}
void baz() {
bif(); // WARNING! Missing REQUIRES(!mu).
}
void bif() REQUIRES(!mu);
};
Negative Capability 通常是默認(rèn)關(guān)閉的,因?yàn)樗鼤?huì)導(dǎo)致已有的代碼產(chǎn)生許多的警告信息??梢酝ㄟ^選項(xiàng) -Wthread-safety-negative 來打開
使用 TSA 的一些注意事項(xiàng)
一般而言,注解通常被當(dāng)作函數(shù)接口的一部分進(jìn)行解析,因此最好放在頭文件當(dāng)中,而不是 .cc 文件當(dāng)中。(NO_THREAD_SAFETY_ANALYSIS 除外)
-
TSA 的解析與檢測主要在編譯期間執(zhí)行,因此不能對(duì)運(yùn)行時(shí)才能確定的條件語句進(jìn)行檢測。例如以下做法是錯(cuò)誤的:
bool b = needsToLock(); if (b) mu.Lock(); ... // Warning! Mutex 'mu' is not held on every path through here. if (b) mu.Unlock(); } -
TSA 僅依賴于函數(shù)的屬性的聲明,它并不會(huì)將函數(shù)調(diào)用展開并內(nèi)聯(lián)到指定位置,因此下面的做法也是錯(cuò)誤的(它使用 mu.lock() 進(jìn)行顯式的上鎖,卻希望使用函數(shù)調(diào)用來進(jìn)行解鎖):
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. -
TSA 無法追蹤指針的指向,因此當(dāng)兩個(gè)指針指向一個(gè)互斥鎖時(shí),會(huì)導(dǎo)致警告的發(fā)生,如下:
class MutexUnlocker { Mutex* mu; public: MutexUnlocker(Mutex* m) RELEASE(m) : mu(m) { mu->Unlock(); } ~MutexUnlocker() ACQUIRE(mu) { mu->Lock(); } }; Mutex mutex; void test() REQUIRES(mutex) { { MutexUnlocker munl(&mutex); // unlocks mutex doSomeIO(); } // Warning: locks munl.mu }mun1 中的成員變量 mu 在析構(gòu)的時(shí)候被釋放,但 TSA 并不能意識(shí)到 mutex 與 mun1.mu 指向了同一個(gè)互斥鎖。因此,會(huì)顯示出警告信息:mun1.mu 未上鎖。