1 #ifndef BOOST_STATECHART_FIFO_WORKER_HPP_INCLUDED 2 #define BOOST_STATECHART_FIFO_WORKER_HPP_INCLUDED 3 ////////////////////////////////////////////////////////////////////////////// 4 // Copyright 2002-2008 Andreas Huber Doenni 5 // Distributed under the Boost Software License, Version 1.0. (See accompany- 6 // ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 ////////////////////////////////////////////////////////////////////////////// 8 9 10 11 #include <boost/assert.hpp> 12 #include <boost/noncopyable.hpp> 13 #include <boost/function/function0.hpp> 14 #include <boost/bind.hpp> 15 // BOOST_HAS_THREADS, BOOST_MSVC 16 #include <boost/config.hpp> 17 18 #include <boost/detail/allocator_utilities.hpp> 19 20 #ifdef BOOST_HAS_THREADS 21 # ifdef BOOST_MSVC 22 # pragma warning( push ) 23 // "conditional expression is constant" in basic_timed_mutex.hpp 24 # pragma warning( disable: 4127 ) 25 // "conversion from 'int' to 'unsigned short'" in microsec_time_clock.hpp 26 # pragma warning( disable: 4244 ) 27 // "... needs to have dll-interface to be used by clients of class ..." 28 # pragma warning( disable: 4251 ) 29 // "... assignment operator could not be generated" 30 # pragma warning( disable: 4512 ) 31 // "Function call with parameters that may be unsafe" in 32 // condition_variable.hpp 33 # pragma warning( disable: 4996 ) 34 # endif 35 36 # include <boost/thread/mutex.hpp> 37 # include <boost/thread/condition.hpp> 38 39 # ifdef BOOST_MSVC 40 # pragma warning( pop ) 41 # endif 42 #endif 43 44 #include <list> 45 #include <memory> // std::allocator 46 47 48 namespace boost 49 { 50 namespace statechart 51 { 52 53 54 55 template< class Allocator = std::allocator< none > > 56 class fifo_worker : noncopyable 57 { 58 public: 59 ////////////////////////////////////////////////////////////////////////// 60 #ifdef BOOST_HAS_THREADS fifo_worker(bool waitOnEmptyQueue=false)61 fifo_worker( bool waitOnEmptyQueue = false ) : 62 waitOnEmptyQueue_( waitOnEmptyQueue ), 63 #else 64 fifo_worker() : 65 #endif 66 terminated_( false ) 67 { 68 } 69 70 typedef function0< void > work_item; 71 72 // We take a non-const reference so that we can move (i.e. swap) the item 73 // into the queue, what avoids copying the (possibly heap-allocated) 74 // implementation object inside work_item. queue_work_item(work_item & item)75 void queue_work_item( work_item & item ) 76 { 77 if ( item.empty() ) 78 { 79 return; 80 } 81 82 #ifdef BOOST_HAS_THREADS 83 mutex::scoped_lock lock( mutex_ ); 84 #endif 85 86 workQueue_.push_back( work_item() ); 87 workQueue_.back().swap( item ); 88 89 #ifdef BOOST_HAS_THREADS 90 queueNotEmpty_.notify_one(); 91 #endif 92 } 93 94 // Convenience overload so that temporary objects can be passed directly 95 // instead of having to create a work_item object first. Under most 96 // circumstances, this will lead to one unnecessary copy of the 97 // function implementation object. queue_work_item(const work_item & item)98 void queue_work_item( const work_item & item ) 99 { 100 work_item copy = item; 101 queue_work_item( copy ); 102 } 103 terminate()104 void terminate() 105 { 106 work_item item = boost::bind( &fifo_worker::terminate_impl, this ); 107 queue_work_item( item ); 108 } 109 110 // Is not mutex-protected! Must only be called from the thread that also 111 // calls operator(). terminated() const112 bool terminated() const 113 { 114 return terminated_; 115 } 116 operator ()(unsigned long maxItemCount=0)117 unsigned long operator()( unsigned long maxItemCount = 0 ) 118 { 119 unsigned long itemCount = 0; 120 121 while ( !terminated() && 122 ( ( maxItemCount == 0 ) || ( itemCount < maxItemCount ) ) ) 123 { 124 work_item item = dequeue_item(); 125 126 if ( item.empty() ) 127 { 128 // item can only be empty when the queue is empty, which only 129 // happens in ST builds or when users pass false to the fifo_worker 130 // constructor 131 return itemCount; 132 } 133 134 item(); 135 ++itemCount; 136 } 137 138 return itemCount; 139 } 140 141 private: 142 ////////////////////////////////////////////////////////////////////////// dequeue_item()143 work_item dequeue_item() 144 { 145 #ifdef BOOST_HAS_THREADS 146 mutex::scoped_lock lock( mutex_ ); 147 148 if ( !waitOnEmptyQueue_ && workQueue_.empty() ) 149 { 150 return work_item(); 151 } 152 153 while ( workQueue_.empty() ) 154 { 155 queueNotEmpty_.wait( lock ); 156 } 157 #else 158 // If the queue happens to run empty in a single-threaded system, 159 // waiting for new work items (which means to loop indefinitely!) is 160 // pointless as there is no way that new work items could find their way 161 // into the queue. The only sensible thing is to exit the loop and 162 // return to the caller in this case. 163 // Users can then queue new work items before calling operator() again. 164 if ( workQueue_.empty() ) 165 { 166 return work_item(); 167 } 168 #endif 169 170 // Optimization: Swap rather than assign to avoid the copy of the 171 // implementation object inside function 172 work_item result; 173 result.swap( workQueue_.front() ); 174 workQueue_.pop_front(); 175 return result; 176 } 177 terminate_impl()178 void terminate_impl() 179 { 180 terminated_ = true; 181 } 182 183 184 typedef std::list< 185 work_item, 186 typename boost::detail::allocator::rebind_to< 187 Allocator, work_item >::type 188 > work_queue_type; 189 190 work_queue_type workQueue_; 191 192 #ifdef BOOST_HAS_THREADS 193 mutex mutex_; 194 condition queueNotEmpty_; 195 const bool waitOnEmptyQueue_; 196 #endif 197 198 bool terminated_; 199 }; 200 201 202 203 } // namespace statechart 204 } // namespace boost 205 206 207 208 #endif 209