While looking through implementations of the std::atomic<T>::wait, I've found that most of them used a simple hash table for mapping the state for each atomic location.
static constexpr size_t __libcpp_contention_table_size = (1 << 8); /* < there's no magic in this number */
struct alignas(64) /* aim to avoid false sharing */ __libcpp_contention_table_entry {
__cxx_atomic_contention_t __contention_state;
__cxx_atomic_contention_t __platform_state;
inline constexpr __libcpp_contention_table_entry() : __contention_state(0), __platform_state(0) {}
};
static __libcpp_contention_table_entry __libcpp_contention_table[__libcpp_contention_table_size];
static __waitable_state&
_S_state_for(const void* __addr) noexcept
{
constexpr __UINTPTR_TYPE__ __ct = 16;
static __waitable_state __w[__ct];
auto __key = ((__UINTPTR_TYPE__)__addr >> 2) % __ct;
return __w[__key];
}
Using the hash table itself is pretty reasonable I think, since atomic wait and notify should be the light weighted lock-free synchronization mechanism. But the size of the hash table is what made me to raise a question.
Since the table itself has a fixed size, I though that it might get some hash collisions when there are enough threads waiting on different locations. And if so, some state might get overlapped, which will cause a false notify on waiting threads. And others might not get notified (or unlocked) which I think is a serious issue.
So here's my questions.
- How is the size of the table determined? (since comment on libcxx says that there are no magic in that number)
- Will there no overlap between multiple atomic variable locations? Or, is it fine to get states overlapped each other?