【6.C++基礎】-鎖

鎖的意義

原子性+可見性
同一時間,只有一個線程執行鎖中代碼 + 鎖內讀在鎖前代碼執行完,寫在鎖釋放前可見html

原子

操做

自己內核的原子是經過原子指令實現的https://code.woboq.org/linux/...
原子庫實現的一下方法能夠帶內存屏障來增強可見性。node

  1. store //原子寫
  2. load //原子讀
  3. exchange //原子交換
  4. compare_exchange_weak //compare and set 性能更高,可是兩個值同樣時可能會意外返回false。a.compare_exchange_weak(&expect,val)。if a=expect,則a.store(v), else expect=a,返回falselinux

    bool compare_exchange_weak (T& expected, T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
    Compares the contents of the atomic object's contained value with expected:
    - if true, it replaces the contained value with val (like store).
    - if false, it replaces expected with the contained value .
    
     __asm__ __volatile__("" : : : "memory");
     inline void* Acquire_Load() const {
        void* result = rep_;
        MemoryBarrier();
        return result;
      }
      inline void Release_Store(void* v) {
        MemoryBarrier();
        rep_ = v;
      }
  5. compare_exchange_strong數組

    2.內存屏障

    typedef enum memory_order {
            memory_order_relaxed, // 不對執行順序作保證
            memory_order_acquire, // A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread (see Release-Acquire ordering below)
            memory_order_release, // A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable (see Release-Acquire ordering below) and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic (see Release-Consume ordering below).
            memory_order_acq_rel, // 同時包含memory_order_acquire 和 memory_order_release
            memory_order_consume, // 本線程中,全部後續的有關本原子類型的操做,必須在本條原子操做完成以後執行
            memory_order_seq_cst // 所有存取都按順序執行
        } memory_order;

    無鎖隊列

    template
    struct Node { T t; shared_ptr<Node> next; };
    atomic<shared_ptr<Node>> head;
    public:
       slist() =default;
       ~slist() =default;
       class reference { 
          shared_ptr p;
       public:
          reference(shared_ptr<Node> p_) : p{_p} {}
          T& operator*() { return p->t; }
          T* operator->() { return &p->t; }
       };
       auto find(T t) const {
          auto p = head.load();
          while (p && p->t != t)
             p = p->next;
          return reference{move(p)};
       void push_front(T t) {
          auto p = make_shared<Node>();
          p->t = t;
          p->next = head;
          while (head.compare_exchange_weak(p->next, p))
             {}
       }
       void pop_front() {
          auto p = head.load();
          while (p && !head.compare_exchange_weak(p, p->next))
             {}
       }
    };

### mutex安全

  • std的mutex =>pthread_mutex_lock
    linux的glibc的pthread包分好幾種,普通的就調futex。自適應的也會先spin。
    循環調用   CAS,wait在futex
    cmpxchgl檢查futex(也就是__lock成員)是否爲0(表示鎖未佔用),如是,賦值1(表示鎖被佔用)
  • pthread_cond_wait:
    也是先釋放mutex。而後futex在cond上(lll_futex_wait (&cond->__data.__futex, futex_val, pshared);)而後再鎖mutex
  • 更多pthread的鎖:https://casatwy.com/pthreadde...

應用

boost和std都有。boost的效率說是比std高一些併發

定義:mutex對象   boost::shared_mutex, boost::mutex
lock_guard,shard_lock,unique_lock都是模板類,用來管理mutex函數

boost::shared_lock<T>中的T只能是shared_mutex類
unique_lock<T>中的T能夠爲mutex類中的任意一種,若是爲shared_mutex,那麼boost::unique_lock<boost::shared_mutex>類的對象構造函數構造時,會自動調用shared_mutex的shared_lock方法,析構函數裏,會自動調用shared_mutex的shared_unlock方法。若是是boost:: unique_lock<boost::mutex>,則分別自動調用lock和unlock方法。 性能

讀寫鎖實現:
typedef boost::shared_lock<boost::shared_mutex> readLock;
typedef boost::unique_lock<boost::shared_mutex> writeLock;
boost::shared_mutex rwmutex;
用的時候:
readLock(rwmutex) 優化

互斥鎖:
typedef boost::unique_lock<boost::mutex> exclusiveLock;
boost::mutex m;
exclusiveLock(m)ui

tips

一寫多讀 多寫多讀 關於coredump這種線程安全都是由於地址訪問,好比要讀的起始被刪除了,數據的reserve啊,map的樹調整啊,rehash啊,直接刪除之類的。而單獨的++這種是不須要的。  

 還有是可見性和原子性。多寫不加鎖(沒有原子性,可見性的保證)會指令亂序覆蓋,好比++的次數變少,讀可能會讀到舊數據,可能做爲if判斷不會當即生效由於在寄存器和另外一個cpucache中。
關於volitale 做用就是禁止編譯器優化,因此取值不會走寄存器。 控制不了別的,因此後面的指令仍是會亂序到他前面,cpu仍是有cpucache,而且cpucache的MSEI沒有指令加鎖也不會原子性,仍然會出現讀不到的狀況。用內存屏障或者老老實實用原子,用鎖,減小鎖衝突

內核原語(spinlocks,mutexes,memory barriers等)確保了併發訪問共享數據的安全,內核原語同時阻止了不須要的優化。若是能正確的使用這些同步原語,固然同時也就沒有必要使用volatile類型。
https://lwn.net/Articles/233482/

barrier();
禁止編譯器指令重排。不使用寄存器的值,從內存中load
(https://zhuanlan.zhihu.com/p/...

spinlock

用戶態和內核處理spin差別很大,內核能控制特定cpu,因此邏輯會複雜不少
用戶態spin還會直接陷入內核阻塞,內核可不會,那就是真的死循環,必須考慮性能

本身寫spinlock

pthread有spin

while (!condition) {  
    if (count > xxx)  break;  
    count++;  
    \_\_asm\_\_ volatile ("pause");  
  }

  mutex();

內核spin

while (lock->locked);    
        lock->locked = 1;    =》不原子=》 while (test_and_set(&lock->locked));  =》while (lock->locked || test_and_set(&lock->locked));
這種寫法每次喚醒lock會出現餓死狀況
引入owner和排隊
struct spinlock {
        unsigned short owner;
        unsigned short next;
};
void spin_lock(struct spinlock *lock)
{
        unsigned short next = xadd(&lock->next, 1);
        while (lock->owner != next);
}
void spin_unlock(struct spinlock *lock)
{
        lock->owner++;
}
在加入spinlock時,會invalid spinlock致使整個cpu cache顛簸。=》每一個cpu本身的結構,用鏈表連接起來
https://zhuanlan.zhihu.com/p/89058726

信號量

信號量
可睡眠,可多個
原來pthread_mutex不支持進程,後來也有了,可是不是全部平臺都支持。信號量是原來進程
加鎖down:在自旋鎖的保護下,加入等待列表,解鎖,調度出去,回來後獲取鎖,檢查是否up,up返回不然循環
解鎖up:在自旋鎖的保護下,去第一個等待列表,刪除,設置up,回調
https://zhuanlan.zhihu.com/p/...
pfs中用來進程同步

ABA

rocksdb中無所隊列ABA問題
若是位置V存儲的是鏈表的頭結點,那麼發生ABA問題的鏈表中,原頭結點是node1,線程 2 操做頭結點變化了兩次,極可能是先修改頭結點爲node2,再將node1(在C++中,也但是從新分配的節點node3,但剛好其指針等於已經釋放掉的node1)插入表頭成爲新的頭結點。

對於線程 1 ,頭結點仍舊爲 node1(或者說頭結點的值,由於在C++中,雖然地址相同,但其內容可能變爲了node3),CAS操做成功,但頭結點以後的子鏈表的狀態已不可預知。

創建一個全局數組 HP hp[N],數組中的元素爲指針,稱爲 Hazard pointer,數組的大小爲線程的數目,即每一個線程擁有一個 HP。
約定每一個線程只能修改本身的 HP,而不容許修改別的線程的 HP,但能夠去讀別的線程的 HP 值。
當線程嘗試去訪問一個關鍵數據節點時,它得先把該節點的指針賦給本身的 HP,即告訴別人不要釋放這個節點。
每一個線程維護一個私有鏈表(free list),當該線程準備釋放一個節點時,把該節點放入本身的鏈表中,當鏈表數目達到一個設定數目 R 後,遍歷該鏈表把能釋放的節點統統釋放。
當一個線程要釋放某個節點時,它須要檢查全局的 HP 數組,肯定若是沒有任何一個線程的 HP 值與當前節點的指針相同,則釋放之,不然不釋放,仍舊把該節點放回本身的鏈表中。
這個不是和文件持有時,其餘不能delete是同樣的。無鎖鏈表在沒有delete時候,next比較。問題是CAS直接取指針比較啊。
https://www.drdobbs.com/lock-...這個能夠解決釋放,至關於維護一個釋放隊列,先不釋放=。=可是解決不了若是再申請仍是這塊內存,CAS比較裏邊值的問題,這個釋放能夠延時,可是賦值不行啊,仍是要帶version啊。

相關文章
相關標籤/搜索