C++20 latch

7168 ワード

vs2019 C++20 latch
  • 01 latch
  • 02 latchの実装
  • 01 latch ラッチ:1回使用するスレッドバリア.latchはptrdiff_tタイプのダウンカウンタで、スレッドを同期するために使用できます.作成時にカウンタの値を初期化します.
    スレッドは、カウンタがゼロに減少するまでlatchでブロックされる可能性があります.カウンタを追加またはリセットすることはできません.これにより、latchは単一使用のバリアになります.
    同時にlatchのメンバー関数を呼び出し、構造関数を除いてデータ競合を導入しません.
    std::barrierとは異なり、参加スレッドはstd::latchを1回以上減らすことができます.1
    方法
    さぎょう
    latch
    割り当て不可
    count_down
    カウンタをブロックせずに削減
    try_wait
    内部カウンタがゼロに等しいかどうかをテストします
    wait
    カウンタがゼロに達するまでブロック
    arrive_and_wait
    カウンタを減らしてゼロに達するまでブロックします
    max
    [静的]サポートされているカウンタの最大値を実現
    latchのリファレンスヘッダファイルstd::latch 2
    namespace std {
      class latch {
      public:
        static constexpr ptrdiff_t max() noexcept;
     
        constexpr explicit latch(ptrdiff_t expected);
        ~latch();
     
        latch(const latch&) = delete;
        latch& operator=(const latch&) = delete;
     
        void count_down(ptrdiff_t update = 1);
        bool try_wait() const noexcept;
        void wait() const;
        void arrive_and_wait(ptrdiff_t update = 1);
     
      private:
        ptrdiff_t counter;  //      
      };
    }
    

    02 latchの実装
    githubの上にlatchの実装(linux,macos,windowsの実装)が見つかりました.https://github.com/luncliff/latch
    ここにwindowsの上の実装が貼られています.開発がまた必要なら、このオープンソースを先に使ったほうがいいです.このlatchはwindows上の実装に重点を置いてWakeByAddressAll,WaitOnAddress,InterlockedAdd,InterlockedAdd64の4つのapiを使用した.latch.h
    #pragma once
    #include 
    
    namespace std {
    /**
     * @defgroup thread.coord
     * Concepts related to thread coordination, and defines the coordination types `latch` and `barrier`.
     * These types facilitate concurrent computation performed by a number of threads.
     */
    
    /**
     * @brief Allows any number of threads to block until an expected number of threads arrive at the latch
     * @ingroup thread.coord
     *
     * A `latch` is a thread coordination mechanism that allows any number of threads
     * to block until an expected number of threads arrive at the `latch`
     * (via the `count_down` function).
     *
     * The expected count is set when the `latch` is created.
     * An individual `latch` is a single-use object;
     * once the expected count has been reached, the `latch` cannot be reused.
     *
     * @see N4835, 1571~1572p
     */
    class latch {
      public:
        /**
         * @brief upper limit on the value of `expected` for constructor of `latch`
         * @return ptrdiff_t    The maximum value of counter that the implementation supports
         * @see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1865r1.html
         * @see /proc/sys/kernel/threads-max
         */
        static constexpr ptrdiff_t max() noexcept {
            return 32;
        }
    
      public:
        /**
         * @brief   Initialize `counter` with `expected`
         * @param   expected
         * @pre     `expected >= 0` is true
         */
        constexpr explicit latch(ptrdiff_t expected) noexcept : counter{expected} {
        }
        /**
         * Concurrent invocations of the member functions of `latch` other than its destructor,
         * do not introduce data races
         */
        ~latch() = default;
    
        latch(const latch&) = delete;
        latch& operator=(const latch&) = delete;
    
        /**
         * **Synchronization**:
         * Strongly happens before the returns from all calls that are unblocked.
         *
         * **Error Conditions**:
         * Any of the error conditions allowed for mutex types (32.5.3.2)
         *
         * @param   update
         * @pre     `update >= 0` is true, and `update <= counter` is true
         * @post    Atomically decreses `counter` by `update`.
         *          If `counter` is equal to zero, unblocks all threads blocked on `*this`
         * @throw   system_error
         */
        void count_down(ptrdiff_t update = 1) noexcept(false);
        /**
         * @return true     `counter` equals zero
         * @return false    Very low probability of failure from system call
         */
        bool try_wait() const noexcept;
        /**
         * If `counter` equals zero, returns immediately.
         * Otherwise, blocks on `*this` until a call to `count_down` that decrements `counter` to zero
         *
         * @throw   system_error
         */
        void wait() const noexcept(false);
        /**
         * @param   update  input for `count_down`
         * @see count_down
         * @see wait
         */
        void arrive_and_wait(ptrdiff_t update = 1) noexcept(false);
    
      private:
        /**
         * @brief A latch maintains an internal counter
         *
         * A latch maintains an internal counter that is initialized when the latch is created
         * Threads can block on the latch object, waiting for counter to be decremented to zero.
         */
        ptrdiff_t counter;
    };
    } // namespace std
    
    latch_windows.cpp
    #include "latch.h"
    
    #include 
    #include 
    #include 
    // clang-format off
    
    #include 
    #include 
    // clang-format on
    
    namespace std {
    
    static_assert(is_copy_assignable_v == false);
    static_assert(is_copy_constructible_v == false);
    
    void latch::arrive_and_wait(ptrdiff_t update) noexcept(false) {
        this->count_down(update);
        this->wait();
    }
    
    void latch::count_down(ptrdiff_t update) noexcept(false) {
        static_assert(is_same_v || is_same_v);
        if (counter < update)
            throw system_error{EINVAL, system_category(),
                               "update is greater than counter"};
        // if not lock-free, rely on InterLocked operation
        if constexpr (atomic::is_always_lock_free) {
            counter -= update;
        } else if constexpr (is_same_v) {
            InterlockedAdd(reinterpret_cast(&counter),
                           static_cast(-update));
        } else if constexpr (is_same_v) {
            InterlockedAdd64(reinterpret_cast(&counter),
                             static_cast(-update));
        }
        // counter reached zero
        if (counter == 0)
            WakeByAddressAll(&counter);
    }
    
    bool latch::try_wait() const noexcept {
        // if counter equals zero, returns immediately
        if (counter == 0)
            return true;
        // blocks on `*this` until a call to count_down that decrements counter to zero
        ptrdiff_t captured = counter;
        if (WaitOnAddress(const_cast(&counter), &captured,
                          sizeof(ptrdiff_t), INFINITE))
            return counter == 0;
        // caller can check `GetLastError` for this case
        return false;
    }
    
    void latch::wait() const noexcept(false) {
        while (try_wait() == false) {
            // case: error from WaitOnAddress
            if (const auto ec = GetLastError())
                throw system_error{static_cast(ec), system_category(),
                                   "WaitOnAddress"};
            // case: counter != 0. retry
            // ...
        }
    }
    
    } // namespace std
    

    std::latch ↩︎
    標準ヘッダファイル↩︎