lockfree 로 multiple producer single consumer queue 를 구현한 코드(https://github.com/mstump/queues/blob/master/include/mpsc-queue.hpp)를 분석하고 있는데 move semantic 도 안쓴 옛날 스타일 같았다.
최신 코드를 찾아봤는데 딱히 업데이트는 없었고 std::aligned_storage 를 사용했는데 placement new/delete 가 없어서 문제될 것 같다는 코멘트가 있었다.
mpsc_queue_t() :
_head(reinterpret_cast<buffer_node_t*>(new buffer_node_aligned_t)),
_tail(_head.load(std::memory_order_relaxed))
{
buffer_node_t* front = _head.load(std::memory_order_relaxed);
front->next.store(NULL, std::memory_order_relaxed);
}
~mpsc_queue_t()
{
T output;
while (this->dequeue(output)) {}
buffer_node_t* front = _head.load(std::memory_order_relaxed);
delete front;
}
void
enqueue(
const T& input)
{
buffer_node_t* node = reinterpret_cast<buffer_node_t*>(new buffer_node_aligned_t);
node->data = input;
node->next.store(NULL, std::memory_order_relaxed);
buffer_node_t* prev_head = _head.exchange(node, std::memory_order_acq_rel);
prev_head->next.store(node, std::memory_order_release);
}
bool
dequeue(
T& output)
{
buffer_node_t* tail = _tail.load(std::memory_order_relaxed);
buffer_node_t* next = tail->next.load(std::memory_order_acquire);
if (next == NULL) {
return false;
}
output = next->data;
_tail.store(next, std::memory_order_release);
delete tail;
return true;
}
private:
struct buffer_node_t
{
T data;
std::atomic<buffer_node_t*> next;
};
typedef typename std::aligned_storage<sizeof(buffer_node_t), std::alignment_of<buffer_node_t>::value>::type buffer_node_aligned_t;
buffer_node_t 의 std::aligned_storage 를 new 로 생성 후 reinterpret_cast 한 다음에 바로 사용하고 있는데 위험해 보이는 코드다.
https://en.cppreference.com/w/cpp/types/aligned_storage
cppreference 의 Note 를 보면 aligned_storage 로 생성한 초기화 안된 공간을 placement new 를 통해 object 를 생성하고 명시적으로 destructor 를 호출한다고 되어 있다.
visual studio 의 std::aligned_storage 구현 코드를 보면 단순히 double 보다 큰 char [] 공간만 잡힌다.
template <class _Ty, size_t _Len>
union _Align_type { // union with size _Len bytes and alignment of _Ty
_Ty _Val;
char _Pad[_Len];
};
template <size_t _Len, size_t _Align, class _Ty>
struct _Aligned<_Len, _Align, _Ty, true> {
using type = _Align_type<_Ty, _Len>;
};
template <size_t _Len, size_t _Align>
struct _Aligned<_Len, _Align, double, false> {
using type = _Align_type<max_align_t, _Len>;
};
...
template <size_t _Len, size_t _Align = alignof(max_align_t)>
struct aligned_storage { // define type with size _Len and alignment _Align
using _Next = char;
static constexpr bool _Fits = _Align <= alignof(_Next);
using type = typename _Aligned<_Len, _Align, _Next, _Fits>::type;
};
new 한 공간을 reinterpret_cast 해서 사용하는게 아니라 placement new 호출 후 사용하는 게 안전하다.
mpsc_queue_t() {
buffer_node_aligned_t* al_st = new buffer_node_aligned_t;
buffer_node_t* node = new (al_st) buffer_node_t();
_head.store(node);
_tail.store(node);
node->next.store(nullptr, std::memory_order_relaxed);
}
~mpsc_queue_t() {
T output;
while (this->dequeue(&output)) {
}
buffer_node_t* front = _head.load(std::memory_order_relaxed);
front->~buffer_node_t();
::operator delete(front);
}
참고 : https://hackage.haskell.org/package/direct-rocksdb-0.0.1/src/rocksdb-5.8/util/mpsc.h