【6.C++ベース】-ロック

8090 ワード

ロックの意味
原子性+可視性は同じ時間で、1つのスレッドだけがロック中のコードを実行します+ロック内はロック前のコードが実行され、ロックが解放される前に表示されます.
げんし
操作
自身のコアの原子は、原子命令によって実現されるhttps://code.woboq.org/linux/...原子ライブラリによって実現される方法であり、メモリバリアを備えて可視性を強化することができる.
  • store//原子書き
  • load//原子読み
  • exchange//原子交換
  • compare_exchange_Weak//compare and setはパフォーマンスが高いが、2つの値が同じ場合、falseが意外に返される可能性がある.a.compare_exchange_weak(&expect,val).if a=expect、a.store(v)、else expect=a、false
    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;
      }
      
  • を返す
  • compare_exchange_strong2.メモリバリア
        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 next; };
    atomic> head;
    public:
       slist() =default;
       ~slist() =default;
       class reference { 
          shared_ptr p;
       public:
          reference(shared_ptr 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();
          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_locklinuxのglibcのpthreadパッケージはいくつかありますが、普通はfutexを調整します.適応も先にspinします.CASをループ呼び出し、waitはfutexcmpxchglで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::mutexlock_guard,shard_lock,unique_lockはすべてテンプレートクラスでmutexを管理するために使用されます
    boost::shared_lockのTはshared_のみmutexクラスunique_lockのTはmutexクラスのいずれかであり、shared_であればmutex,boost::unique_lock<:shared_mutex>クラスのオブジェクト構築関数構築時にshared_が自動的に呼び出されます.mutexのshared_lockメソッド、解析関数でshared_が自動的に呼び出されますmutexのshared_unlockメソッド.boost::unique_lock<:mutex>では、lockメソッドとunlockメソッドがそれぞれ自動的に呼び出されます.
    読み書きロック実装:typedef boost::shared_lock<:shared_mutex> readLock; typedef boost::unique_lock<:shared_mutex> writeLock; boost::shared_mutex rwmutex;使用時:readLock(rwmutex)
    反発ロック:typedef boost::unique_lock<:mutex> exclusiveLock;boost::mutex m;exclusiveLock(m)
    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が直接カーネルのブロックに陥ることもあります.カーネルはできません.それは本当に死のサイクルであり、性能を考慮しなければなりません.
    自分でスピンロックを書く
    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問題が発生したチェーンテーブルでは、元のヘッダノードがnode 1であり、スレッド2の操作でヘッダノードが2回変化し、ヘッダノードがnode 2であることを先に修正する可能性が高いさらにnode 1(C++においても再割り当てのノードnode 3であるが,そのポインタは既に解放されたnode 1に等しい)をヘッダに挿入して新しいヘッダノードとする.
    スレッド1の場合、ヘッダノードは依然としてnode 1(または、C++ではアドレスが同じであるが、その内容がnode 3になる可能性があるため、ヘッダノードの値)であり、CAS操作は成功したが、ヘッダノードの後のサブチェーンテーブルの状態は予知できない.
    グローバル配列HP hp[N]を確立し、配列中の要素をポインタとし、Hazard pointerと呼ばれ、配列の大きさはスレッドの数、すなわちスレッドごとにHPを持つ.各スレッドは自分のHPしか修正できないことを約束し、他のスレッドのHPを修正することは許されないが、他のスレッドのHP値を読むことができる.スレッドが重要なデータノードにアクセスしようとすると、ノードのポインタを自分のHPに割り当てなければなりません.つまり、このノードを解放しないように他の人に伝えなければなりません.各スレッドは1つのプライベートチェーンテーブル(free list)を維持し、スレッドが1つのノードを解放する準備ができている場合、そのノードを自分のチェーンテーブルに入れ、チェーンテーブルの数が1つの設定数Rに達した後、チェーンテーブルを遍歴して解放可能なノードを解放する.スレッドがノードを解放する場合、グローバルなHP配列を確認し、現在のノードのポインタと同じスレッド値がない場合は解放され、そうでない場合は解放されず、そのノードを自分のチェーンテーブルに戻す必要があります.これはファイルが持っている場合ではなく、他のdeleteと同じではありません.無ロックチェーンテーブルはdeleteがない場合、nextが比較されます.問題はCASが直接ポインタを取って比較することでしょう.https://www.drdobbs.com/lock-...これは解放を解決することができ、解放キューを維持することに相当し、解放しない=.しかし、再申請がこのメモリであれば、CASが内部値を比較する問題は解決できません.この解放は遅延できますが、付与はできません.versionを持っていきましょう.