clang 的線程安全分析筆記

宏的意義

  • 一些編譯器宏的使用方法(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)

  1. 一般而言,注解通常被當(dāng)作函數(shù)接口的一部分進(jìn)行解析,因此最好放在頭文件當(dāng)中,而不是 .cc 文件當(dāng)中。(NO_THREAD_SAFETY_ANALYSIS 除外)

  2. 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();
    }
    
  3. 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.
    
  4. 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 未上鎖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容