Source code analysis of mpmcqueue

Time:2020-11-29

GitHub address: https://github.com/rigtorp/MP…

  • about__cpp_lib_hardware_interference_size

This function test macro represents the new feature introduced by C + + 17 https://zh.cppreference.com/w…
relevant: https://www.it1352.com/178422…
It means the minimum offset between two objects to avoid false sharing.
About false sharing: https://www.cnblogs.com/cyfon…

#ifdef __cpp_lib_hardware_interference_size
static constexpr size_t hardwareInterferenceSize = std::hardware_destructive_interference_size;
#else
static constexpr size_t hardwareInterferenceSize = 64;
#endif

The above code obtains the minimum offset to avoid pseudo sharing under the current hardware conditions. If the compiler has not implemented this feature, it is defined as 64 bytes. (the mainstream hardware architecture cache line size is 64 bytes: https://blog.csdn.net/midion9… )

In order to avoid pseudo sharing, let different objects be in different cache lines, that is, set the offset to the cache line size.

  • about__cpp_aligned_new

The new features introduced by C + + 17 are as follows: https://www.jianshu.com/p/7ce…
Its purpose is to provide a memory aligned version of the new operator.

#if defined(__cpp_aligned_new)
template <typename T> using AlignedAllocator = std::allocator<T>;
#else
template <typename T> struct AlignedAllocator {
  using value_type = T;
  T *allocate(std::size_t n) {
    if (n > std::numeric_limits<std::size_t>::max() / sizeof(T)) {
      throw std::bad_array_new_length();
    }
#ifdef _WIN32
    auto *p = static_cast<T*>(_aligned_malloc(sizeof(T) * n, alignof(T)));
    if (p == nullptr) {
      throw std::bad_alloc();
    }
#else
    T *p;
    if (posix_memalign(reinterpret_cast<void **>(&p), alignof(T), sizeof(T) * n) != 0) {
      throw std::bad_alloc();
    }
#endif
    return p;
  }

  void deallocate(T *p, std::size_t) {
#ifdef WIN32
    _aligned_free(p);
#else
    free(p);
#endif
  }
};
#endif

If the compiler implements the aligned new feature, it will directly implement the memory allocator of the standard library version, otherwise
Customize one and use it according to different platforms_ aligned_ malloc/posix_ Memalign implementation.

  • On slot class
template <typename T> struct Slot {
~Slot() noexcept {
  if (turn & 1) {
    destroy();
  }
}

template <typename... Args> void construct(Args &&... args) noexcept {
static_assert(std::is_nothrow_constructible<T, Args &&...>::value, "T must be nothrow constructible with Args&&...");
  new (&storage) T(std::forward<Args>(args)...);
}

void destroy() noexcept {
static_assert(std::is_nothrow_destructible<T>::value, "T must be nothrow destructible");
  reinterpret_cast<T*>(&storage)->~T();
}

T &&move() noexcept { return reinterpret_cast<T &&>(storage); }

// Align to avoid false sharing between adjacent slots
alignas(hardwareInterferenceSize) std::atomic<size_t> turn = {0};
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage;
};

The above is the implementation of the slot class, focusing on the following aspects:

  1. Modifier meaning of data member turn
  2. The role of data member storage
  3. Destructor
  4. Placement new operation in construct function

usestd::aligned_ Storage defines an object storage with a maximum size of sizeof (T) and an alignof (T) requirement. The T-type object is constructed on the structure function by placement new operation.
The purpose of this series of operations is to provide the user-defined abstract ability of memory application and construction according to any alignment requirements
As with any other uninitialized storage, the objects are created using placement new and destroyed with explicit destructor calls.
Reference articles: https://blog.csdn.net/ywcpig/… and
https://en.cppreference.com/w…
useThe alignas modifier modifies the alignment requirement of the data member turn to hardwardinterferencesize, which is the constant defined in the first part of the article. Since the size of the structure must be an integer multiple of the maximum alignment number of its data members, the storage of different slot objects cannot exist in the same cache line, avoiding false sharing.
DestructorAccording to whether turn is 0 or not, the destroy function is selectively called to destruct the object. You can see that the other function of turn is to mark whether there is still a class t object in this slot.
tips:
Students who are not proficient in C + + syntax can take a look at this code, which involves curly bracket initialization, in class initialization, xvalue generation, perfect forwarding of variable parameters, noexcept and other new standard features.

Recommended Today

Summary of recent use of gin

Recently, a new project is developed by using gin. Some problems are encountered in the process. To sum up, as a note, I hope it can help you. Cross domain problems Middleware: func Cors() gin.HandlerFunc { return func(c *gin.Context) { //Here you can use * or the domain name you specify c.Header(“Access-Control-Allow-Origin”, “*”) //Allow header […]