Added copyright and license
[libcds.git] / cds / container / fcdeque.h
1 /*
2     This file is a part of libcds - Concurrent Data Structures library
3
4     (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2016
5
6     Source code repo: http://github.com/khizmax/libcds/
7     Download: http://sourceforge.net/projects/libcds/files/
8     
9     Redistribution and use in source and binary forms, with or without
10     modification, are permitted provided that the following conditions are met:
11
12     * Redistributions of source code must retain the above copyright notice, this
13       list of conditions and the following disclaimer.
14
15     * Redistributions in binary form must reproduce the above copyright notice,
16       this list of conditions and the following disclaimer in the documentation
17       and/or other materials provided with the distribution.
18
19     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20     AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21     IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23     FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24     DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26     CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27     OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28     OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.     
29 */
30
31 #ifndef CDSLIB_CONTAINER_FCDEQUE_H
32 #define CDSLIB_CONTAINER_FCDEQUE_H
33
34 #include <cds/algo/flat_combining.h>
35 #include <cds/algo/elimination_opt.h>
36 #include <deque>
37
38 namespace cds { namespace container {
39
40     /// FCDeque related definitions
41     /** @ingroup cds_nonintrusive_helper
42     */
43     namespace fcdeque {
44
45         /// FCDeque internal statistics
46         template <typename Counter = cds::atomicity::event_counter >
47         struct stat: public cds::algo::flat_combining::stat<Counter>
48         {
49             typedef cds::algo::flat_combining::stat<Counter>    flat_combining_stat; ///< Flat-combining statistics
50             typedef typename flat_combining_stat::counter_type  counter_type;        ///< Counter type
51
52             counter_type    m_nPushFront     ;  ///< Count of push_front operations
53             counter_type    m_nPushFrontMove ;  ///< Count of push_front operations with move semantics
54             counter_type    m_nPushBack      ;  ///< Count of push_back operations
55             counter_type    m_nPushBackMove  ;  ///< Count of push_back operations with move semantics
56             counter_type    m_nPopFront      ;  ///< Count of success pop_front operations
57             counter_type    m_nFailedPopFront;  ///< Count of failed pop_front operations (pop from empty deque)
58             counter_type    m_nPopBack       ;  ///< Count of success pop_back operations
59             counter_type    m_nFailedPopBack ;  ///< Count of failed pop_back operations (pop from empty deque)
60             counter_type    m_nCollided      ;  ///< How many pairs of push/pop were collided, if elimination is enabled
61
62             //@cond
63             void    onPushFront()             { ++m_nPushFront; }
64             void    onPushFrontMove()         { ++m_nPushFrontMove; }
65             void    onPushBack()              { ++m_nPushBack; }
66             void    onPushBackMove()          { ++m_nPushBackMove; }
67             void    onPopFront( bool bFailed ) { if ( bFailed ) ++m_nFailedPopFront; else ++m_nPopFront;  }
68             void    onPopBack( bool bFailed ) { if ( bFailed ) ++m_nFailedPopBack; else ++m_nPopBack;  }
69             void    onCollide()               { ++m_nCollided; }
70             //@endcond
71         };
72
73         /// FCDeque dummy statistics, no overhead
74         struct empty_stat: public cds::algo::flat_combining::empty_stat
75         {
76             //@cond
77             void    onPushFront()       {}
78             void    onPushFrontMove()   {}
79             void    onPushBack()        {}
80             void    onPushBackMove()    {}
81             void    onPopFront(bool)    {}
82             void    onPopBack(bool)     {}
83             void    onCollide()         {}
84             //@endcond
85         };
86
87         /// FCDeque type traits
88         struct traits: public cds::algo::flat_combining::traits
89         {
90             typedef empty_stat      stat;   ///< Internal statistics
91             static CDS_CONSTEXPR const bool enable_elimination = false; ///< Enable \ref cds_elimination_description "elimination"
92         };
93
94         /// Metafunction converting option list to traits
95         /**
96             \p Options are:
97             - \p opt::lock_type - mutex type, default is \p cds::sync::spin
98             - \p opt::back_off - back-off strategy, defalt is \p cds::backoff::delay_of<2>
99             - \p opt::allocator - allocator type, default is \ref CDS_DEFAULT_ALLOCATOR
100             - \p opt::stat - internal statistics, possible type: \ref stat, \ref empty_stat (the default)
101             - \p opt::memory_model - C++ memory ordering model.
102                 List of all available memory ordering see opt::memory_model.
103                 Default if cds::opt::v:relaxed_ordering
104             - \p opt::enable_elimination - enable/disable operation \ref cds_elimination_description "elimination"
105                 By default, the elimination is disabled. For queue, the elimination is possible if the queue
106                 is empty.
107         */
108         template <typename... Options>
109         struct make_traits {
110 #   ifdef CDS_DOXYGEN_INVOKED
111             typedef implementation_defined type ;   ///< Metafunction result
112 #   else
113             typedef typename cds::opt::make_options<
114                 typename cds::opt::find_type_traits< traits, Options... >::type
115                 ,Options...
116             >::type   type;
117 #   endif
118         };
119
120     } // namespace fcqueue
121
122     /// Flat-combining deque
123     /**
124         @ingroup cds_nonintrusive_deque
125         @ingroup cds_flat_combining_container
126
127         \ref cds_flat_combining_description "Flat combining" sequential deque.
128         The class can be considered as a concurrent FC-based wrapper for \p std::deque.
129
130         Template parameters:
131         - \p T - a value type stored in the deque
132         - \p Deque - sequential deque implementation, for example, \p std::deque<T> (the default)
133             or \p boost::container::deque
134         - \p Trats - type traits of flat combining, default is \p fcdeque::traits.
135             \p fcdeque::make_traits metafunction can be used to construct specialized \p %fcdeque::traits
136     */
137     template <typename T,
138         class Deque = std::deque<T>,
139         typename Traits = fcdeque::traits
140     >
141     class FCDeque
142 #ifndef CDS_DOXYGEN_INVOKED
143         : public cds::algo::flat_combining::container
144 #endif
145     {
146     public:
147         typedef T           value_type;     ///< Value type
148         typedef Deque       deque_type;     ///< Sequential deque class
149         typedef Traits      traits;         ///< Deque type traits
150
151         typedef typename traits::stat  stat;   ///< Internal statistics type
152         static CDS_CONSTEXPR const bool c_bEliminationEnabled = traits::enable_elimination; ///< \p true if elimination is enabled
153
154     protected:
155         //@cond
156         /// Deque operation IDs
157         enum fc_operation {
158             op_push_front = cds::algo::flat_combining::req_Operation, ///< Push front
159             op_push_front_move,     ///< Push front (move semantics)
160             op_push_back,           ///< Push back
161             op_push_back_move,      ///< Push back (move semantics)
162             op_pop_front,           ///< Pop front
163             op_pop_back,            ///< Pop back
164             op_clear,               ///< Clear
165             op_empty                ///< Empty
166         };
167
168         /// Flat combining publication list record
169         struct fc_record: public cds::algo::flat_combining::publication_record
170         {
171             union {
172                 value_type const *  pValPush; ///< Value to push
173                 value_type *        pValPop;  ///< Pop destination
174             };
175             bool            bEmpty; ///< \p true if the deque is empty
176         };
177         //@endcond
178
179         /// Flat combining kernel
180         typedef cds::algo::flat_combining::kernel< fc_record, traits > fc_kernel;
181
182     protected:
183         //@cond
184         fc_kernel   m_FlatCombining;
185         deque_type  m_Deque;
186         //@endcond
187
188     public:
189         /// Initializes empty deque object
190         FCDeque()
191         {}
192
193         /// Initializes empty deque object and gives flat combining parameters
194         FCDeque(
195             unsigned int nCompactFactor     ///< Flat combining: publication list compacting factor
196             ,unsigned int nCombinePassCount ///< Flat combining: number of combining passes for combiner thread
197             )
198             : m_FlatCombining( nCompactFactor, nCombinePassCount )
199         {}
200
201         /// Inserts a new element at the beginning of the deque container
202         /**
203             The function always returns \p true
204         */
205         bool push_front(
206             value_type const& val ///< Value to be copied to inserted element
207         )
208         {
209             fc_record * pRec = m_FlatCombining.acquire_record();
210             pRec->pValPush = &val;
211
212             if ( c_bEliminationEnabled )
213                 m_FlatCombining.batch_combine( op_push_front, pRec, *this );
214             else
215                 m_FlatCombining.combine( op_push_front, pRec, *this );
216
217             assert( pRec->is_done() );
218             m_FlatCombining.release_record( pRec );
219             m_FlatCombining.internal_statistics().onPushFront();
220             return true;
221         }
222
223         /// Inserts a new element at the beginning of the deque container (move semantics)
224         /**
225             The function always returns \p true
226         */
227         bool push_front(
228             value_type&& val ///< Value to be moved to inserted element
229         )
230         {
231             fc_record * pRec = m_FlatCombining.acquire_record();
232             pRec->pValPush = &val;
233
234             if ( c_bEliminationEnabled )
235                 m_FlatCombining.batch_combine( op_push_front_move, pRec, *this );
236             else
237                 m_FlatCombining.combine( op_push_front_move, pRec, *this );
238
239             assert( pRec->is_done() );
240             m_FlatCombining.release_record( pRec );
241             m_FlatCombining.internal_statistics().onPushFrontMove();
242             return true;
243         }
244
245         /// Inserts a new element at the end of the deque container
246         /**
247             The function always returns \p true
248         */
249         bool push_back(
250             value_type const& val ///< Value to be copied to inserted element
251         )
252         {
253             fc_record * pRec = m_FlatCombining.acquire_record();
254             pRec->pValPush = &val;
255
256             if ( c_bEliminationEnabled )
257                 m_FlatCombining.batch_combine( op_push_back, pRec, *this );
258             else
259                 m_FlatCombining.combine( op_push_back, pRec, *this );
260
261             assert( pRec->is_done() );
262             m_FlatCombining.release_record( pRec );
263             m_FlatCombining.internal_statistics().onPushBack();
264             return true;
265         }
266
267         /// Inserts a new element at the end of the deque container (move semantics)
268         /**
269             The function always returns \p true
270         */
271         bool push_back(
272             value_type&& val ///< Value to be moved to inserted element
273         )
274         {
275             fc_record * pRec = m_FlatCombining.acquire_record();
276             pRec->pValPush = &val;
277
278             if ( c_bEliminationEnabled )
279                 m_FlatCombining.batch_combine( op_push_back_move, pRec, *this );
280             else
281                 m_FlatCombining.combine( op_push_back_move, pRec, *this );
282
283             assert( pRec->is_done() );
284             m_FlatCombining.release_record( pRec );
285             m_FlatCombining.internal_statistics().onPushBackMove();
286             return true;
287         }
288
289         /// Removes the first element in the deque container
290         /**
291             The function returns \p false if the deque is empty, \p true otherwise.
292             If the deque is empty \p val is not changed.
293         */
294         bool pop_front(
295             value_type& val ///< Target to be received the copy of removed element
296         )
297         {
298             fc_record * pRec = m_FlatCombining.acquire_record();
299             pRec->pValPop = &val;
300
301             if ( c_bEliminationEnabled )
302                 m_FlatCombining.batch_combine( op_pop_front, pRec, *this );
303             else
304                 m_FlatCombining.combine( op_pop_front, pRec, *this );
305
306             assert( pRec->is_done() );
307             m_FlatCombining.release_record( pRec );
308             m_FlatCombining.internal_statistics().onPopFront( pRec->bEmpty );
309             return !pRec->bEmpty;
310         }
311
312         /// Removes the last element in the deque container
313         /**
314             The function returns \p false if the deque is empty, \p true otherwise.
315             If the deque is empty \p val is not changed.
316         */
317         bool pop_back(
318             value_type& val ///< Target to be received the copy of removed element
319         )
320         {
321             fc_record * pRec = m_FlatCombining.acquire_record();
322             pRec->pValPop = &val;
323
324             if ( c_bEliminationEnabled )
325                 m_FlatCombining.batch_combine( op_pop_back, pRec, *this );
326             else
327                 m_FlatCombining.combine( op_pop_back, pRec, *this );
328
329             assert( pRec->is_done() );
330             m_FlatCombining.release_record( pRec );
331             m_FlatCombining.internal_statistics().onPopBack( pRec->bEmpty );
332             return !pRec->bEmpty;
333         }
334
335         /// Clears the deque
336         void clear()
337         {
338             fc_record * pRec = m_FlatCombining.acquire_record();
339
340             if ( c_bEliminationEnabled )
341                 m_FlatCombining.batch_combine( op_clear, pRec, *this );
342             else
343                 m_FlatCombining.combine( op_clear, pRec, *this );
344
345             assert( pRec->is_done() );
346             m_FlatCombining.release_record( pRec );
347         }
348
349         /// Returns the number of elements in the deque.
350         /**
351             Note that <tt>size() == 0</tt> is not mean that the deque is empty because
352             combining record can be in process.
353             To check emptiness use \ref empty function.
354         */
355         size_t size() const
356         {
357             return m_Deque.size();
358         }
359
360         /// Checks if the deque is empty
361         /**
362             If the combining is in process the function waits while combining done.
363         */
364         bool empty()
365         {
366             fc_record * pRec = m_FlatCombining.acquire_record();
367
368             if ( c_bEliminationEnabled )
369                 m_FlatCombining.batch_combine( op_empty, pRec, *this );
370             else
371                 m_FlatCombining.combine( op_empty, pRec, *this );
372
373             assert( pRec->is_done() );
374             m_FlatCombining.release_record( pRec );
375             return pRec->bEmpty;
376         }
377
378         /// Internal statistics
379         stat const& statistics() const
380         {
381             return m_FlatCombining.statistics();
382         }
383
384     public: // flat combining cooperation, not for direct use!
385         //@cond
386         /// Flat combining supporting function. Do not call it directly!
387         /**
388             The function is called by \ref cds::algo::flat_combining::kernel "flat combining kernel"
389             object if the current thread becomes a combiner. Invocation of the function means that
390             the deque should perform an action recorded in \p pRec.
391         */
392         void fc_apply( fc_record * pRec )
393         {
394             assert( pRec );
395
396             // this function is called under FC mutex, so switch TSan off
397             CDS_TSAN_ANNOTATE_IGNORE_RW_BEGIN;
398
399             switch ( pRec->op() ) {
400             case op_push_front:
401                 assert( pRec->pValPush );
402                 m_Deque.push_front( *(pRec->pValPush) );
403                 break;
404             case op_push_front_move:
405                 assert( pRec->pValPush );
406                 m_Deque.push_front( std::move( *(pRec->pValPush )) );
407                 break;
408             case op_push_back:
409                 assert( pRec->pValPush );
410                 m_Deque.push_back( *(pRec->pValPush) );
411                 break;
412             case op_push_back_move:
413                 assert( pRec->pValPush );
414                 m_Deque.push_back( std::move( *(pRec->pValPush )) );
415                 break;
416             case op_pop_front:
417                 assert( pRec->pValPop );
418                 pRec->bEmpty = m_Deque.empty();
419                 if ( !pRec->bEmpty ) {
420                     *(pRec->pValPop) = m_Deque.front();
421                     m_Deque.pop_front();
422                 }
423                 break;
424             case op_pop_back:
425                 assert( pRec->pValPop );
426                 pRec->bEmpty = m_Deque.empty();
427                 if ( !pRec->bEmpty ) {
428                     *(pRec->pValPop) = m_Deque.back();
429                     m_Deque.pop_back();
430                 }
431                 break;
432             case op_clear:
433                 while ( !m_Deque.empty() )
434                     m_Deque.pop_front();
435                 break;
436             case op_empty:
437                 pRec->bEmpty = m_Deque.empty();
438                 break;
439             default:
440                 assert(false);
441                 break;
442             }
443             CDS_TSAN_ANNOTATE_IGNORE_RW_END;
444         }
445
446         /// Batch-processing flat combining
447         void fc_process( typename fc_kernel::iterator itBegin, typename fc_kernel::iterator itEnd )
448         {
449             typedef typename fc_kernel::iterator fc_iterator;
450
451             // this function is called under FC mutex, so switch TSan off
452             CDS_TSAN_ANNOTATE_IGNORE_RW_BEGIN;
453
454             for ( fc_iterator it = itBegin, itPrev = itEnd; it != itEnd; ++it ) {
455                 switch ( it->op() ) {
456                 case op_push_front:
457                 case op_push_front_move:
458                     if ( itPrev != itEnd
459                       && (itPrev->op() == op_pop_front || ( m_Deque.empty() && itPrev->op() == op_pop_back )))
460                     {
461                         collide( *it, *itPrev );
462                         itPrev = itEnd;
463                     }
464                     else
465                         itPrev = it;
466                     break;
467                 case op_push_back:
468                 case op_push_back_move:
469                     if ( itPrev != itEnd
470                         && (itPrev->op() == op_pop_back || ( m_Deque.empty() && itPrev->op() == op_pop_front )))
471                     {
472                         collide( *it, *itPrev );
473                         itPrev = itEnd;
474                     }
475                     else
476                         itPrev = it;
477                     break;
478                 case op_pop_front:
479                     if ( itPrev != itEnd
480                         && ( itPrev->op() == op_push_front || itPrev->op() == op_push_front_move
481                           || ( m_Deque.empty() && ( itPrev->op() == op_push_back || itPrev->op() == op_push_back_move ))))
482                     {
483                         collide( *itPrev, *it );
484                         itPrev = itEnd;
485                     }
486                     else
487                         itPrev = it;
488                     break;
489                 case op_pop_back:
490                     if ( itPrev != itEnd
491                         && ( itPrev->op() == op_push_back || itPrev->op() == op_push_back_move
492                         || ( m_Deque.empty() && ( itPrev->op() == op_push_front || itPrev->op() == op_push_front_move ))))
493                     {
494                         collide( *itPrev, *it );
495                         itPrev = itEnd;
496                     }
497                     else
498                         itPrev = it;
499                     break;
500                 }
501             }
502             CDS_TSAN_ANNOTATE_IGNORE_RW_END;
503         }
504         //@endcond
505
506     private:
507         //@cond
508         void collide( fc_record& recPush, fc_record& recPop )
509         {
510             *(recPop.pValPop) = *(recPush.pValPush);
511             recPop.bEmpty = false;
512             m_FlatCombining.operation_done( recPush );
513             m_FlatCombining.operation_done( recPop );
514             m_FlatCombining.internal_statistics().onCollide();
515         }
516         //@endcond
517     };
518
519 }} // namespace cds::container
520
521 #endif // #ifndef CDSLIB_CONTAINER_FCDEQUE_H