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
的工作原理,我很满意了解wait
、notify_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_VARIABLE
而wait
的本质是调用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
问题:
std::condition_variable
为 CONDITION_VARIABLE
保留 72 个字节(见编辑 2)。这72个字节有什么用?
std::condition_variable
怎么能用更少的字节逃脱呢?看起来好像在某些机器上 std::condition_variable
s 只有 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 回退。它仍然与通常的配置共享实现,显然是出于维护原因。
标题问题的较长版本是:
On my machine,
sizeof(std::condition_variable)
is 72 bytes. What are these 72 bytes used for?
注意:std::condition_variable
的大小取决于实现。 附录 A 中给出了一些示例尺寸。
要了解std::condition_variable
的工作原理,我很满意了解wait
、notify_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_VARIABLE
而wait
的本质是调用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
问题:
std::condition_variable
为CONDITION_VARIABLE
保留 72 个字节(见编辑 2)。这72个字节有什么用?std::condition_variable
怎么能用更少的字节逃脱呢?看起来好像在某些机器上std::condition_variable
s 只有 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 回退。它仍然与通常的配置共享实现,显然是出于维护原因。