#ifndef BOOST_STATECHART_FIFO_WORKER_HPP_INCLUDED | |
#define BOOST_STATECHART_FIFO_WORKER_HPP_INCLUDED | |
////////////////////////////////////////////////////////////////////////////// | |
// Copyright 2002-2008 Andreas Huber Doenni | |
// Distributed under the Boost Software License, Version 1.0. (See accompany- | |
// ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
////////////////////////////////////////////////////////////////////////////// | |
#include <boost/assert.hpp> | |
#include <boost/noncopyable.hpp> | |
#include <boost/function/function0.hpp> | |
#include <boost/bind.hpp> | |
// BOOST_HAS_THREADS, BOOST_MSVC | |
#include <boost/config.hpp> | |
#include <boost/detail/allocator_utilities.hpp> | |
#ifdef BOOST_HAS_THREADS | |
# ifdef BOOST_MSVC | |
# pragma warning( push ) | |
// "conditional expression is constant" in basic_timed_mutex.hpp | |
# pragma warning( disable: 4127 ) | |
// "conversion from 'int' to 'unsigned short'" in microsec_time_clock.hpp | |
# pragma warning( disable: 4244 ) | |
// "... needs to have dll-interface to be used by clients of class ..." | |
# pragma warning( disable: 4251 ) | |
// "... assignment operator could not be generated" | |
# pragma warning( disable: 4512 ) | |
// "Function call with parameters that may be unsafe" in | |
// condition_variable.hpp | |
# pragma warning( disable: 4996 ) | |
# endif | |
# include <boost/thread/mutex.hpp> | |
# include <boost/thread/condition.hpp> | |
# ifdef BOOST_MSVC | |
# pragma warning( pop ) | |
# endif | |
#endif | |
#include <list> | |
#include <memory> // std::allocator | |
namespace boost | |
{ | |
namespace statechart | |
{ | |
template< class Allocator = std::allocator< void > > | |
class fifo_worker : noncopyable | |
{ | |
public: | |
////////////////////////////////////////////////////////////////////////// | |
#ifdef BOOST_HAS_THREADS | |
fifo_worker( bool waitOnEmptyQueue = false ) : | |
waitOnEmptyQueue_( waitOnEmptyQueue ), | |
#else | |
fifo_worker() : | |
#endif | |
terminated_( false ) | |
{ | |
} | |
typedef function0< void > work_item; | |
// We take a non-const reference so that we can move (i.e. swap) the item | |
// into the queue, what avoids copying the (possibly heap-allocated) | |
// implementation object inside work_item. | |
void queue_work_item( work_item & item ) | |
{ | |
if ( item.empty() ) | |
{ | |
return; | |
} | |
#ifdef BOOST_HAS_THREADS | |
mutex::scoped_lock lock( mutex_ ); | |
#endif | |
workQueue_.push_back( work_item() ); | |
workQueue_.back().swap( item ); | |
#ifdef BOOST_HAS_THREADS | |
queueNotEmpty_.notify_one(); | |
#endif | |
} | |
// Convenience overload so that temporary objects can be passed directly | |
// instead of having to create a work_item object first. Under most | |
// circumstances, this will lead to one unnecessary copy of the | |
// function implementation object. | |
void queue_work_item( const work_item & item ) | |
{ | |
work_item copy = item; | |
queue_work_item( copy ); | |
} | |
void terminate() | |
{ | |
work_item item = boost::bind( &fifo_worker::terminate_impl, this ); | |
queue_work_item( item ); | |
} | |
// Is not mutex-protected! Must only be called from the thread that also | |
// calls operator(). | |
bool terminated() const | |
{ | |
return terminated_; | |
} | |
unsigned long operator()( unsigned long maxItemCount = 0 ) | |
{ | |
unsigned long itemCount = 0; | |
while ( !terminated() && | |
( ( maxItemCount == 0 ) || ( itemCount < maxItemCount ) ) ) | |
{ | |
work_item item = dequeue_item(); | |
if ( item.empty() ) | |
{ | |
// item can only be empty when the queue is empty, which only | |
// happens in ST builds or when users pass false to the fifo_worker | |
// constructor | |
return itemCount; | |
} | |
item(); | |
++itemCount; | |
} | |
return itemCount; | |
} | |
private: | |
////////////////////////////////////////////////////////////////////////// | |
work_item dequeue_item() | |
{ | |
#ifdef BOOST_HAS_THREADS | |
mutex::scoped_lock lock( mutex_ ); | |
if ( !waitOnEmptyQueue_ && workQueue_.empty() ) | |
{ | |
return work_item(); | |
} | |
while ( workQueue_.empty() ) | |
{ | |
queueNotEmpty_.wait( lock ); | |
} | |
#else | |
// If the queue happens to run empty in a single-threaded system, | |
// waiting for new work items (which means to loop indefinitely!) is | |
// pointless as there is no way that new work items could find their way | |
// into the queue. The only sensible thing is to exit the loop and | |
// return to the caller in this case. | |
// Users can then queue new work items before calling operator() again. | |
if ( workQueue_.empty() ) | |
{ | |
return work_item(); | |
} | |
#endif | |
// Optimization: Swap rather than assign to avoid the copy of the | |
// implementation object inside function | |
work_item result; | |
result.swap( workQueue_.front() ); | |
workQueue_.pop_front(); | |
return result; | |
} | |
void terminate_impl() | |
{ | |
terminated_ = true; | |
} | |
typedef std::list< | |
work_item, | |
typename boost::detail::allocator::rebind_to< | |
Allocator, work_item >::type | |
> work_queue_type; | |
work_queue_type workQueue_; | |
#ifdef BOOST_HAS_THREADS | |
mutex mutex_; | |
condition queueNotEmpty_; | |
const bool waitOnEmptyQueue_; | |
#endif | |
bool terminated_; | |
}; | |
} // namespace statechart | |
} // namespace boost | |
#endif |