CONDITION_VARIABLE是如何实现的?

How is CONDITION_VARIABLE implemented?

标题问题的较长版本是:

On my machine, sizeof(std::condition_variable) is 72 bytes. What are these 72 bytes used for?

注意:std::condition_variable的大小取决于实现。 附录 A 中给出了一些示例尺寸。

要了解std::condition_variable的工作原理,我很满意了解waitnotify_one和成员object。我将从 wait 开始。 wait 带谓词如下。

    template <class _Predicate>
    void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) { // wait for signal and test predicate
        while (!_Pred()) {
            wait(_Lck);
        }
    }

上面的wait调用了no-predicatewait.

    void wait(unique_lock<mutex>& _Lck) { // wait for signal
        // Nothing to do to comply with LWG-2135 because std::mutex lock/unlock are nothrow
        _Cnd_wait(_Mycnd(), _Lck.mutex()->_Mymtx());
    }

此等待在 _Mycnd() 上调用 _Cnd_wait_Cnd_wait 被发现 here.

int _Cnd_wait(const _Cnd_t cond, const _Mtx_t mtx) { // wait until signaled
    const auto cs = static_cast<Concurrency::details::stl_critical_section_interface*>(_Mtx_getconcrtcs(mtx));
    _Mtx_clear_owner(mtx);
    cond->_get_cv()->wait(cs);
    _Mtx_reset_owner(mtx);
    return _Thrd_success; // TRANSITION, ABI: Always returns _Thrd_success
}

_Cnd_t 是指向 _Cnd_internal_imp_t .

的指针
using _Cnd_t = struct _Cnd_internal_imp_t*;

结构 _Cnd_internal_imp_t 定义为 here

struct _Cnd_internal_imp_t { // condition variable implementation for ConcRT
    std::aligned_storage_t<Concurrency::details::stl_condition_variable_max_size,
        Concurrency::details::stl_condition_variable_max_alignment>
        cv;

    [[nodiscard]] Concurrency::details::stl_condition_variable_interface* _get_cv() noexcept {
        // get pointer to implementation
        return reinterpret_cast<Concurrency::details::stl_condition_variable_interface*>(&cv);
    }
};

我现在正在查看 cond->_get_cv()->wait(cs); 行。要理解这一行,我需要查看 Concurrency::details::stl_condition_variable_interface 的成员 wait 函数。这是一个virtual function.

        class __declspec(novtable) stl_condition_variable_interface {
        public:
            virtual void wait(stl_critical_section_interface*)                   = 0;
            virtual bool wait_for(stl_critical_section_interface*, unsigned int) = 0;
            virtual void notify_one()                                            = 0;
            virtual void notify_all()                                            = 0;
            virtual void destroy()                                               = 0;
        };

编辑 2

cond->_get_cv() 是指向抽象 class stl_condition_variable_interface 的指针。在构建过程中的某个时刻,create_stl_condition_variable will be called to set the virtual pointer. The virtual pointer for this object will point to the vtable for either stl_condition_variable_vista given here or stl_condition_variable_win7 given here. The top answer to this 堆栈溢出问题解释了一些细节。

在我的例子中,虚拟指针指向 stl_condition_variable_win7 的 table。

        class stl_condition_variable_win7 final : public stl_condition_variable_interface {
        public:
            stl_condition_variable_win7() {
                InitializeConditionVariable(&m_condition_variable);
            }

            ~stl_condition_variable_win7()                                  = delete;
            stl_condition_variable_win7(const stl_condition_variable_win7&) = delete;
            stl_condition_variable_win7& operator=(const stl_condition_variable_win7&) = delete;

            void destroy() override {}

            void wait(stl_critical_section_interface* lock) override {
                if (!stl_condition_variable_win7::wait_for(lock, INFINITE)) {
                    std::terminate();
                }
            }

            bool wait_for(stl_critical_section_interface* lock, unsigned int timeout) override {
                return SleepConditionVariableSRW(&m_condition_variable,
                           static_cast<stl_critical_section_win7*>(lock)->native_handle(), timeout, 0)
                    != 0;
            }

            void notify_one() override {
                WakeConditionVariable(&m_condition_variable);
            }

            void notify_all() override {
                WakeAllConditionVariable(&m_condition_variable);
            }

        private:
            CONDITION_VARIABLE m_condition_variable;
        };

所以我的72或8个字节被保留来存储一个CONDITION_VARIABLEwait的本质是调用SleepConditionVariableSRW。此函数描述为 here

结束编辑 2

附录 A

std::condition_variable的唯一成员object是

aligned_storage_t<_Cnd_internal_imp_size, _Cnd_internal_imp_alignment> _Cnd_storage;

std::condition_variable 包含下面的成员函数,它允许 _Cnd_storage 被解释为 _Cnd_t.

    _Cnd_t _Mycnd() noexcept { // get pointer to _Cnd_internal_imp_t inside _Cnd_storage
        return reinterpret_cast<_Cnd_t>(&_Cnd_storage);
    }

sizeof(std::condition_variable)sizeof(_Cnd_storage)给出,定义在xthreads.h.

// Size and alignment for _Mtx_internal_imp_t and _Cnd_internal_imp_t
#ifdef _CRT_WINDOWS
#ifdef _WIN64
_INLINE_VAR constexpr size_t _Mtx_internal_imp_size      = 32;
_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 8;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_size      = 16;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 8;
#else // _WIN64
_INLINE_VAR constexpr size_t _Mtx_internal_imp_size      = 20;
_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 4;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_size      = 8;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 4;
#endif // _WIN64
#else // _CRT_WINDOWS
#ifdef _WIN64
_INLINE_VAR constexpr size_t _Mtx_internal_imp_size      = 80;
_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 8;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_size      = 72;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 8;
#else // _WIN64
_INLINE_VAR constexpr size_t _Mtx_internal_imp_size      = 48;
_INLINE_VAR constexpr size_t _Mtx_internal_imp_alignment = 4;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_size      = 40;
_INLINE_VAR constexpr size_t _Cnd_internal_imp_alignment = 4;
#endif // _WIN64
#endif // _CRT_WINDOWS

编辑 1/附录 B

我在发布问题后想到了这个问题,但我不确定如何让它与其他问题一起流动。 std::condition_variable的唯一成员是

aligned_storage_t<_Cnd_internal_imp_size, _Cnd_internal_imp_alignment> _Cnd_storage;

解释为 _Cnd_internal_imp_t_Cnd_internal_imp_t 的唯一成员是

std::aligned_storage_t<Concurrency::details::stl_condition_variable_max_size, Concurrency::details::stl_condition_variable_max_alignment> cv;

有可能stl_condition_variable_max_size != _Cnd_internal_imp_size。其实这隐含在这个line

static_assert(sizeof(_Cnd_internal_imp_t) <= _Cnd_internal_imp_size, "incorrect _Cnd_internal_imp_size");

这意味着 72 个字节中的一些可能是“未使用的”。

结束编辑 1

问题:

  1. std::condition_variableCONDITION_VARIABLE 保留 72 个字节(见编辑 2)。这72个字节有什么用?
  2. std::condition_variable 怎么能用更少的字节逃脱呢?看起来好像在某些机器上 std::condition_variables 只有 8 个字节大。看:
    _INLINE_VAR constexpr size_t _Cnd_internal_imp_size      = 8;
    

这是一个不完整的答案,但确实提供了更多信息。

std::condition_variable 的构造函数调用一个函数,该函数在 _Cnd_storage

中创建条件变量的实现
condition_variable() {
  _Cnd_init_in_situ(_Mycnd());
}

当定义了_CRT_WINDOWS时,好像里面存放的数据是2个指针,或者说是一个指针和一个指针大小的整数;其中第一个可能是一个虚函数指针(指向stl_condition_variable_interface),另一个是状态。

根据您使用的 OS 和库提供的内容,条件变量实现中需要或多或少的机制。

该实现可能位于您似乎无权访问的源代码中。

https://github.com/ojdkbuild/tools_toolchain_vs2017bt_1416/blob/master/VC/Tools/MSVC/14.16.27023/crt/src/stl/cond.c 似乎是 _Cnd_init_in_situ,只是转发到 Concurrency::details::create_stl_condition_variable(cond->_get_cv()).

Here 是 VS2013 Concurrency::detalis::_Condition_variable。但是,它似乎不是在那里创建的(它没有虚拟基础)。它有两个成员:

void * volatile _M_pWaitChain;
Concurrency::critical_section _M_lock;

这可能与实际存储在那里的内容相似(因为它是类似内容的先前实现)。关键部分对于 std condition_variable 可能是多余的,因为它有一个外部互斥体可以使用。

_M_pWaitChain 中的内容我不能说,除了它的名字。

这一切还没有完成。我知道现代条件变量知道如果它们持有锁,它们何时会收到信号,并与释放互斥锁时唤醒的线程进行交互;即,OS 调度内容的低级别内部。

std::condition_variable reserves 72 bytes for a CONDITION_VARIABLE (see Edit 2). What are these 72 bytes used for?

还有另一种由并发运行时 (ConcRT) 支持的条件变量实现。在 Visual Studio 2012 年,它是唯一的实现,但结果并不是很好。

从 VS 2015 开始,实际 CONDITION_VARIABLE 支持更好的实现。有一种多态性可以为不同的 Windows 版本创建不同的实现,因为 CONDITION_VARIABLE 从 Windows Vista 开始可用,并且 complete SRWLOCK 从 Windows 7 开始可用。多态性使用 placement new 而不是联合来隐藏实现细节并使实现符合

所以,有多个实现的地方,其中 ConcRT 是最大的。

否则,sizeof(CONDITION_VARIABLE) == sizeof(void*),以及 sizeof(SRWLOCK) == sizeof(void*),尽管它们不是内部指针。如果使用 CONDITION_VARIABLE / SRWLOCK 实现,则剩余的大小将被浪费。

从Visual Studio2019开始,VS工具集不再支持WindowsXP(VS 2019通过安装VS 2017工具集支持)。因此,ConcRT 依赖性和创建 pre-Vista condition_variable 的能力已被 my PR. A follow-up PR 移除了 ConcRT 结构包装器。

从 Visual Studio 2022 年开始,Windows VS 工具集也不再支持 Vista,my other PR 正在删除 SRWLOCK 多态性。

仍然由于 VS 2015、VS 2017、VS 2019 和 VS 2022 之间的 ABI 兼容性,无法减小 condition_variable 的大小。

摆脱 mutex 构造函数中的新位置并修复 mutex 构造函数非 constexpr 的一致性问题也很困难 (my attempt has failed)。

所以,VS 2019和VS 2022还是要预留space用于ConcRT实现,不再使用

随着 Visual Studio 的下一个 ABI 中断版本,condition_variable 的实现很可能会发生变化。


How could a std::condition_variable get away with fewer bytes?

_CRT_WINDOWS 实现从来不需要支持 Windows XP,因此没有 ConcRT 回退。它仍然与通常的配置共享实现,显然是出于维护原因。