Boost logo

Boost Users :

Subject: [Boost-users] lockfree : getting a move only type into spsc_queue
From: Thomas Novotny (tnovotny_at_[hidden])
Date: 2016-07-23 17:36:10


  I had a unique_ptr from a pool with a custom deleter that I needed to
get int a lockfree queue. This did not work out of the box with either
boost::lockfree::queue or boost::lockfree::spsc_queue.

  There were three main issues:
  * default construction
  * copying
  * copy construction

  To solve the issue with default constructability I had to wrap the
unique_ptr in a class.

  In the end Imade a custom version of spsc_queue. Getting this working
(as a hack) was fairly easy, so I wanted to share it in the hope that a
better solution might make it into a future version of the library.

  In essence, I only had to add some std::move calls and adjust the api
for push.

two lines here:

   bool push(T const & t, T * buffer, size_t max_size)
      {
          const size_t write_index =
write_index_.load(memory_order_relaxed); // only written from push thread
          const size_t next = next_index(write_index, max_size);

          if (next == read_index_.load(memory_order_acquire))
              return false; /* ringbuffer is full */

          new (buffer + write_index) T(t); // copy-construct

          write_index_.store(next, memory_order_release);

          return true;
      }

  to

   bool push( T & t, T * buffer, size_t max_size )
      {
          const size_t write_index = write_index_.load(
memory_order_relaxed ); // only written from push thread
          const size_t next = next_index( write_index, max_size );

          if( next == read_index_.load( memory_order_acquire ) )
              return false; /* ringbuffer is full */

          new (buffer + write_index) T(); // default-construct
          *(buffer + write_index) = std::move( t ); // move

          write_index_.store( next, memory_order_release );

          return true;
      }

  a line here:

  bool push(T const & t)
      {
          return base_type::push(t);
      }

  to

  bool push(T & t)
      {
          return base_type::push(t);
      }

and a few lines here

  template< class OutputIterator >
      OutputIterator copy_and_delete( T * first, T * last,
OutputIterator out )
      {
          if (boost::has_trivial_destructor<T>::value) {
              return std::copy(first, last, out); // will use memcpy if
possible
          } else {
              for (; first != last; ++first, ++out) {
                  *out = *first;
                  first->~T();
              }
              return out;
          }
      }

  to

  template< class OutputIterator >
      OutputIterator copy_and_delete( T * first, T * last,
OutputIterator out )
      {
              for (; first != last; ++first, ++out) {
                  *out = std::move(*first);
                  first->~T();
              }
              return out;
      }

  In copy_payload there is only one change

  struct copy_convertible
  {
      template <typename T, typename U>
      static void copy(T & t, U & u)
      {
          u = t;
      }
  };

  became

  template <typename T, typename U>
      static void copy(T & t, U & u)
      {
          u = std::move(t);
      }

and that was it. The result is not pretty but works (only single element
push and pop used).


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net