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

 

std::aligned_storage - cppreference.com

(since C++11) (deprecated in C++23) Provides the nested type type, which is a trivial standard-layout type suitable for use as uninitialized storage for any object whose size is at most Len and whose alignment requirement is a divisor of Align. The default

en.cppreference.com

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

 

728x90

+ Recent posts