c++反発とstd::call_once

3438 ワード

テキストリンク:https://www.zhihu.com/search?type=content&q=unique_lock
スレッド間で共有データにアクセスするには、反発ロックによって同期する必要があります.同じ時点で1つのスレッドのみがアクセスできることを保証します(または1つのスレッドのみが書き込み操作を行う).C++11以降、標準ライブラリはstd::mutexを提供して開発者の反発ロックに対する需要を満たし、recursive_mutex、timed_mutex、shared_mutexなど、関連するバリエーションがたくさんあります.mutexは複製できず、移動できません(move).
  • std::recursive_mutexは再帰ロックであり、mutexとの違いは、再帰ロックの内部にカウンタがあり、同じスレッドが複数回ロックを解除することを許可し、ロックを解除した回数が相当する場合にのみ、共有リソースの占有権を本当に解放することである.注意:recursiveを乱用しないでください.mutex.便利に見えますが、コードの本当の問題を隠すため、デバッグが非常に困難になります.必要でなければ、使わないでください.
  • std::timed_mutexは時間制御付きmutexでtry_が追加されましたlock_forとtry_lock_untilの2つの方法.とmutexのtry_lockに比べてタイムアウト判定が増加した.それ以外に特に注意することはありません.
  • std::shared_mutexは読み書きロックで、共有性(shared)と排他性の2つのアクセス権の制御(exclusive).lock/try_lockで排他的アクセス権を取得し、lock_shared/try_lock_sharedで共有的アクセス権を取得します.このような設定は、異なるスレッドを区別する読み書き操作に特に役立ちます.shared_mutexはc++17に導入されており、コンパイラバージョンに注意する必要があります.
  • 手動でlock/unlockを使用してロックを加減するのは良い方法ではありません.人は間違いを犯すので、unlockを書き逃しやすく、特に複雑な論理判断の中で.そのため、標準ライブラリはRAIIのメカニズムを提供しています.lock_のようにguard, scoped_lock,unique_lock,shared_ロックなど.lock_guardとscoped_lock、両者の作用は似ていて、すべてmutexのwrapperで、両者の違いはscoped_ですlockは同時にlock複数のmutexをサポートし、lock_guardはサポートされていません.またxxx_lockはlockメソッドとunlockメソッドを手動で呼び出すことをサポートし、lock_guardはサポートしていません.lockのように聞こえます.guardの動作はRAIIの定義に合致している.実際、scoped_lockはc++17規格に導入され、lock_guardはdeprecatedされる方法のようだ.xxx_lockにはlockメソッドとunlockメソッドがあり、そのライフサイクルが終了する前にunlockを呼び出すことができます.
    unique_lockは、構築時に異なるlockingポリシーを指定することもできます.
    std::mutex g;
    std::unique_lock<:mutex> lg(g, std::defer_lock()); // std::adopt_lock, std::try_to_lock
    

    一般的なポリシーは3つあります:defer_lock:mutexの所有権をすぐに取得しません.このポリシーは、std::lock同時lock複数unique_lock. try_to_lock_t:mutexの所有権を取得しようとします.adopt_lock_t:スレッドがmutexの所有権を他の場所で取得したと仮定します.
    開発の過程で、ある関数が一度だけ呼び出されることを保証する必要がある場合があります.この需要は、スレッドの安全な単一のクラスの作成でよく発生します.従来のmutexの書き方は以下の通りで、2回の検査でinstance_リソースが一度だけ申請されることを確認します.このような方法をDouble-Checked Locking Patternと呼ぶ.実際にDCLPの方法もマルチスレッド環境の共有リソース保護の問題を解決することができない(具体的な原因はScottのC++and the Perils of Double-Checked Lockingを参照し、中国語資料の参考https://blog.csdn.net/tantexian/article/details/50684689)で、その主な原因はinstance_.reset(new Singleton)は原子操作ではなく、コンパイラが3つの文に変換して実現し、DCLPメソッドの失敗を招く可能性があります.
    class Singleton {
    public:
      static Singleton& GetInstance() {
        if (!instance_) {
          std::lock_guard<:mutex> lock(mutex_);
          if (!instance_) {
            instance_.reset(new Singleton);
          }
        }
        return *instance_;
      }
    
      ~Singleton() = default;
    
    private:
      Singleton() = default;
    
      Singleton(const Singleton&) = delete;
      Singleton& operator=(const Singleton&) = delete;
    
    private:
      static std::unique_ptr instance_;
      static std::mutex mutex_;
    };
    

    この問題に対して、c++11はstd::call_を導入した.once,着信std::once_flagは,関数がマルチスレッド環境で一度だけ実行されることを確保し,上記の問題を完璧に解決する.このように、言語レベルのソリューションは十分に簡潔であることがわかります.
    class Singleton {
    public:
      static Singleton& GetInstance() {
        static std::once_flag s_flag;
        std::call_once(s_flag, [&]() {
          instance_.reset(new Singleton);
        });
    
        return *instance_;
      }
    
      ~Singleton() = default;
    
    private:
      Singleton() = default;
    
      Singleton(const Singleton&) = delete;
      Singleton& operator=(const Singleton&) = delete;
    
    private:
      static std::unique_ptr instance_;
    };
    

    リンク:https://zhuanlan.zhihu.com/p/77999255