// Copyright (C) 2008-2013 Tim Blechmann // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_LOCKFREE_STACK_HPP_INCLUDED #define BOOST_LOCKFREE_STACK_HPP_INCLUDED #include #include #include #ifdef BOOST_NO_CXX11_DELETED_FUNCTIONS #include #endif #include #include #include #include #include #include #include #include #include namespace boost { namespace lockfree { namespace detail { typedef parameter::parameters, boost::parameter::optional > stack_signature; } /** The stack class provides a multi-writer/multi-reader stack, pushing and popping is lock-free, * construction/destruction has to be synchronized. It uses a freelist for memory management, * freed nodes are pushed to the freelist and not returned to the OS before the stack is destroyed. * * \b Policies: * * - \c boost::lockfree::fixed_sized<>, defaults to \c boost::lockfree::fixed_sized
* Can be used to completely disable dynamic memory allocations during push in order to ensure lockfree behavior.
* If the data structure is configured as fixed-sized, the internal nodes are stored inside an array and they are addressed * by array indexing. This limits the possible size of the stack to the number of elements that can be addressed by the index * type (usually 2**16-2), but on platforms that lack double-width compare-and-exchange instructions, this is the best way * to achieve lock-freedom. * * - \c boost::lockfree::capacity<>, optional
* If this template argument is passed to the options, the size of the stack is set at compile-time.
* It this option implies \c fixed_sized * * - \c boost::lockfree::allocator<>, defaults to \c boost::lockfree::allocator>
* Specifies the allocator that is used for the internal freelist * * \b Requirements: * - T must have a copy constructor * */ #ifndef BOOST_DOXYGEN_INVOKED template #else template #endif class stack #ifdef BOOST_NO_CXX11_DELETED_FUNCTIONS : boost::noncopyable #endif { private: #ifndef BOOST_DOXYGEN_INVOKED BOOST_STATIC_ASSERT(boost::has_trivial_assign::value); BOOST_STATIC_ASSERT(boost::has_trivial_destructor::value); typedef typename detail::stack_signature::bind::type bound_args; static const bool has_capacity = detail::extract_capacity::has_capacity; static const size_t capacity = detail::extract_capacity::capacity; static const bool fixed_sized = detail::extract_fixed_sized::value; static const bool node_based = !(has_capacity || fixed_sized); static const bool compile_time_sized = has_capacity; struct node { node(T const & val): v(val) {} typedef typename detail::select_tagged_handle::handle_type handle_t; handle_t next; const T v; }; typedef typename detail::extract_allocator::type node_allocator; typedef typename detail::select_freelist::type pool_t; typedef typename pool_t::tagged_node_handle tagged_node_handle; // check compile-time capacity BOOST_STATIC_ASSERT((mpl::if_c::const_max>, mpl::true_ >::type::value)); struct implementation_defined { typedef node_allocator allocator; typedef std::size_t size_type; }; #endif #ifndef BOOST_NO_CXX11_DELETED_FUNCTIONS stack(stack const &) = delete; stack(stack &&) = delete; const stack& operator=( const stack& ) = delete; #endif public: typedef T value_type; typedef typename implementation_defined::allocator allocator; typedef typename implementation_defined::size_type size_type; /** * \return true, if implementation is lock-free. * * \warning It only checks, if the top stack node and the freelist can be modified in a lock-free manner. * On most platforms, the whole implementation is lock-free, if this is true. Using c++0x-style atomics, * there is no possibility to provide a completely accurate implementation, because one would need to test * every internal node, which is impossible if further nodes will be allocated from the operating system. * * */ bool is_lock_free (void) const { return tos.is_lock_free() && pool.is_lock_free(); } //! Construct stack // @{ stack(void): pool(node_allocator(), capacity) { BOOST_ASSERT(has_capacity); initialize(); } template explicit stack(typename node_allocator::template rebind::other const & alloc): pool(alloc, capacity) { BOOST_STATIC_ASSERT(has_capacity); initialize(); } explicit stack(allocator const & alloc): pool(alloc, capacity) { BOOST_ASSERT(has_capacity); initialize(); } // @} //! Construct stack, allocate n nodes for the freelist. // @{ explicit stack(size_type n): pool(node_allocator(), n) { BOOST_ASSERT(!has_capacity); initialize(); } template stack(size_type n, typename node_allocator::template rebind::other const & alloc): pool(alloc, n) { BOOST_STATIC_ASSERT(!has_capacity); initialize(); } // @} /** Allocate n nodes for freelist * * \pre only valid if no capacity<> argument given * \note thread-safe, may block if memory allocator blocks * * */ void reserve(size_type n) { BOOST_STATIC_ASSERT(!has_capacity); pool.template reserve(n); } /** Allocate n nodes for freelist * * \pre only valid if no capacity<> argument given * \note not thread-safe, may block if memory allocator blocks * * */ void reserve_unsafe(size_type n) { BOOST_STATIC_ASSERT(!has_capacity); pool.template reserve(n); } /** Destroys stack, free all nodes from freelist. * * \note not thread-safe * * */ ~stack(void) { T dummy; while(unsynchronized_pop(dummy)) {} } private: #ifndef BOOST_DOXYGEN_INVOKED void initialize(void) { tos.store(tagged_node_handle(pool.null_handle(), 0)); } void link_nodes_atomic(node * new_top_node, node * end_node) { tagged_node_handle old_tos = tos.load(detail::memory_order_relaxed); for (;;) { tagged_node_handle new_tos (pool.get_handle(new_top_node), old_tos.get_tag()); end_node->next = pool.get_handle(old_tos); if (tos.compare_exchange_weak(old_tos, new_tos)) break; } } void link_nodes_unsafe(node * new_top_node, node * end_node) { tagged_node_handle old_tos = tos.load(detail::memory_order_relaxed); tagged_node_handle new_tos (pool.get_handle(new_top_node), old_tos.get_tag()); end_node->next = pool.get_pointer(old_tos); tos.store(new_tos, memory_order_relaxed); } template tuple prepare_node_list(ConstIterator begin, ConstIterator end, ConstIterator & ret) { ConstIterator it = begin; node * end_node = pool.template construct(*it++); if (end_node == NULL) { ret = begin; return make_tuple(NULL, NULL); } node * new_top_node = end_node; end_node->next = NULL; try { /* link nodes */ for (; it != end; ++it) { node * newnode = pool.template construct(*it); if (newnode == NULL) break; newnode->next = new_top_node; new_top_node = newnode; } } catch (...) { for (node * current_node = new_top_node; current_node != NULL;) { node * next = current_node->next; pool.template destruct(current_node); current_node = next; } throw; } ret = it; return make_tuple(new_top_node, end_node); } #endif public: /** Pushes object t to the stack. * * \post object will be pushed to the stack, if internal node can be allocated * \returns true, if the push operation is successful. * * \note Thread-safe. If internal memory pool is exhausted and the memory pool is not fixed-sized, a new node will be allocated * from the OS. This may not be lock-free. * \throws if memory allocator throws * */ bool push(T const & v) { return do_push(v); } /** Pushes object t to the stack. * * \post object will be pushed to the stack, if internal node can be allocated * \returns true, if the push operation is successful. * * \note Thread-safe and non-blocking. If internal memory pool is exhausted, the push operation will fail * */ bool bounded_push(T const & v) { return do_push(v); } #ifndef BOOST_DOXYGEN_INVOKED private: template bool do_push(T const & v) { node * newnode = pool.template construct(v); if (newnode == 0) return false; link_nodes_atomic(newnode, newnode); return true; } template ConstIterator do_push(ConstIterator begin, ConstIterator end) { node * new_top_node; node * end_node; ConstIterator ret; tie(new_top_node, end_node) = prepare_node_list(begin, end, ret); if (new_top_node) link_nodes_atomic(new_top_node, end_node); return ret; } public: #endif /** Pushes as many objects from the range [begin, end) as freelist node can be allocated. * * \return iterator to the first element, which has not been pushed * * \note Operation is applied atomically * \note Thread-safe. If internal memory pool is exhausted and the memory pool is not fixed-sized, a new node will be allocated * from the OS. This may not be lock-free. * \throws if memory allocator throws */ template ConstIterator push(ConstIterator begin, ConstIterator end) { return do_push(begin, end); } /** Pushes as many objects from the range [begin, end) as freelist node can be allocated. * * \return iterator to the first element, which has not been pushed * * \note Operation is applied atomically * \note Thread-safe and non-blocking. If internal memory pool is exhausted, the push operation will fail * \throws if memory allocator throws */ template ConstIterator bounded_push(ConstIterator begin, ConstIterator end) { return do_push(begin, end); } /** Pushes object t to the stack. * * \post object will be pushed to the stack, if internal node can be allocated * \returns true, if the push operation is successful. * * \note Not thread-safe. If internal memory pool is exhausted and the memory pool is not fixed-sized, a new node will be allocated * from the OS. This may not be lock-free. * \throws if memory allocator throws * */ bool unsynchronized_push(T const & v) { node * newnode = pool.template construct(v); if (newnode == 0) return false; link_nodes_unsafe(newnode, newnode); return true; } /** Pushes as many objects from the range [begin, end) as freelist node can be allocated. * * \return iterator to the first element, which has not been pushed * * \note Not thread-safe. If internal memory pool is exhausted and the memory pool is not fixed-sized, a new node will be allocated * from the OS. This may not be lock-free. * \throws if memory allocator throws */ template ConstIterator unsynchronized_push(ConstIterator begin, ConstIterator end) { node * new_top_node; node * end_node; ConstIterator ret; tie(new_top_node, end_node) = prepare_node_list(begin, end, ret); if (new_top_node) link_nodes_unsafe(new_top_node, end_node); return ret; } /** Pops object from stack. * * \post if pop operation is successful, object will be copied to ret. * \returns true, if the pop operation is successful, false if stack was empty. * * \note Thread-safe and non-blocking * * */ bool pop(T & ret) { return pop(ret); } /** Pops object from stack. * * \pre type T must be convertible to U * \post if pop operation is successful, object will be copied to ret. * \returns true, if the pop operation is successful, false if stack was empty. * * \note Thread-safe and non-blocking * * */ template bool pop(U & ret) { BOOST_STATIC_ASSERT((boost::is_convertible::value)); tagged_node_handle old_tos = tos.load(detail::memory_order_consume); for (;;) { node * old_tos_pointer = pool.get_pointer(old_tos); if (!old_tos_pointer) return false; tagged_node_handle new_tos(old_tos_pointer->next, old_tos.get_next_tag()); if (tos.compare_exchange_weak(old_tos, new_tos)) { detail::copy_payload(old_tos_pointer->v, ret); pool.template destruct(old_tos); return true; } } } /** Pops object from stack. * * \post if pop operation is successful, object will be copied to ret. * \returns true, if the pop operation is successful, false if stack was empty. * * \note Not thread-safe, but non-blocking * * */ bool unsynchronized_pop(T & ret) { return unsynchronized_pop(ret); } /** Pops object from stack. * * \pre type T must be convertible to U * \post if pop operation is successful, object will be copied to ret. * \returns true, if the pop operation is successful, false if stack was empty. * * \note Not thread-safe, but non-blocking * * */ template bool unsynchronized_pop(U & ret) { BOOST_STATIC_ASSERT((boost::is_convertible::value)); tagged_node_handle old_tos = tos.load(detail::memory_order_relaxed); node * old_tos_pointer = pool.get_pointer(old_tos); if (!pool.get_pointer(old_tos)) return false; node * new_tos_ptr = pool.get_pointer(old_tos_pointer->next); tagged_node_handle new_tos(pool.get_handle(new_tos_ptr), old_tos.get_next_tag()); tos.store(new_tos, memory_order_relaxed); detail::copy_payload(old_tos_pointer->v, ret); pool.template destruct(old_tos); return true; } /** consumes one element via a functor * * pops one element from the stack and applies the functor on this object * * \returns true, if one element was consumed * * \note Thread-safe and non-blocking, if functor is thread-safe and non-blocking * */ template bool consume_one(Functor & f) { T element; bool success = pop(element); if (success) f(element); return success; } /// \copydoc boost::lockfree::stack::consume_one(Functor & rhs) template bool consume_one(Functor const & f) { T element; bool success = pop(element); if (success) f(element); return success; } /** consumes all elements via a functor * * sequentially pops all elements from the stack and applies the functor on each object * * \returns number of elements that are consumed * * \note Thread-safe and non-blocking, if functor is thread-safe and non-blocking * */ template size_t consume_all(Functor & f) { size_t element_count = 0; while (consume_one(f)) element_count += 1; return element_count; } /// \copydoc boost::lockfree::stack::consume_all(Functor & rhs) template size_t consume_all(Functor const & f) { size_t element_count = 0; while (consume_one(f)) element_count += 1; return element_count; } /** * \return true, if stack is empty. * * \note It only guarantees that at some point during the execution of the function the stack has been empty. * It is rarely practical to use this value in program logic, because the stack can be modified by other threads. * */ bool empty(void) const { return pool.get_pointer(tos.load()) == NULL; } private: #ifndef BOOST_DOXYGEN_INVOKED detail::atomic tos; static const int padding_size = BOOST_LOCKFREE_CACHELINE_BYTES - sizeof(tagged_node_handle); char padding[padding_size]; pool_t pool; #endif }; } /* namespace lockfree */ } /* namespace boost */ #endif /* BOOST_LOCKFREE_STACK_HPP_INCLUDED */