//------------------------------------------------------------------------------ /* This file is part of Beast: https://github.com/vinniefalco/Beast Copyright 2013, Vinnie Falco Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #ifndef BEAST_CALLQUEUE_BEASTHEADER #define BEAST_CALLQUEUE_BEASTHEADER /*============================================================================*/ /** A FIFO for calling functors asynchronously. This object is an alternative to traditional locking techniques used to implement concurrent systems. Instead of acquiring a mutex to change shared data, a functor is queued for later execution (usually on another thread). The execution of the functor applies the transformation to the shared state that was formerly performed within a lock (i.e. CriticalSection). For read operations on shared data, instead of acquiring a mutex and accessing the data directly, copies are made (one for each thread), and the thread accesses its copy without acquiring a lock. One thread owns the master copy of the shared state. Requests for changing shared state are made by other threads by posting functors to the master thread's CallQueue. The master thread notifies other threads of changes by posting functors to their respective associated CallQueue, using the Listeners interface. The purpose of the functor is to encapsulate one mutation of shared state to guarantee progress towards a consensus of the concurrent data among participating threads. Functors should execute quickly, ideally in constant time. Dynamically allocated objects of class type passed as functor parameters should, in general, be reference counted. The ConcurrentObject class is ideal for meeting this requirement, and has the additional benefit that the workload of deletion is performed on a separate, provided thread. This queue is not a replacement for a thread pool or job queue type system. A CallQueue is considered signaled when one or more functors are present. Functors are executed during a call to synchronize(). The operation of executing functors via the call to synchronize() is called synchronizing the queue. It can more generally be thought of as synchronizing multiple copies of shared data between threads. Although there is some extra work required to set up and maintain this system, the benefits are significant. Since shared data is only synchronized at well defined times, the programmer can reason and make strong statements about the correctness of the concurrent system. For example, if an AudioIODeviceCallback synchronizes the CallQueue only at the beginning of its execution, it is guaranteed that shared data will remain the same throughout the remainder of the function. Because shared data is accessed for reading without a lock, upper bounds on the run time performance can easily be calculated and assured. Compare this with the use of a mutex - the run time performance experiences a combinatorial explosion of possibilities depending on the complex interaction of multiple threads. Since a CallQueue is almost always used to invoke parameterized member functions of objects, the call() function comes in a variety of convenient forms to make usage easy: @code void func1 (int); struct Object { void func2 (void); void func3 (String name); static void func4 (); }; CallQueue fifo ("Example"); void example () { fifo.call (func1, 42); // same as: func1 (42) Object* object = new Object; fifo.call (&Object::func2, object); // same as: object->func2 () fifo.call (&Object::func3, // same as: object->funcf ("Label") object, "Label"); fifo.call (&Object::func4); // even static members can be called. fifo.callf (functional::bind (&Object::func2, // same as: object->func2 () object)); } @endcode @invariant Functors can be added from any thread at any time, to any queue which is not closed. @invariant When synchronize() is called, functors are called and deleted. @invariant The thread from which synchronize() is called is considered the thread associated with the CallQueue. @invariant Functors queued by the same thread always execute in the same order they were queued. @invariant Functors are guaranteed to execute. It is an error if the CallQueue is deleted while there are functors in it. Normally, you will not use CallQueue directly, but one of its subclasses instead. The CallQueue is one of a handful of objects that work together to implement this system of concurrent data access. For performance considerations, this implementation is wait-free for producers and mostly wait-free for consumers. It also uses a lock-free and wait-free (in the fast path) custom memory allocator. @see GuiCallQueue, ManualCallQueue, MessageThread, ThreadWithCallQueue @ingroup beast_concurrent */ class BEAST_API CallQueue { public: //============================================================================ /** Type of allocator to use. @internal */ typedef FifoFreeStoreType AllocatorType; /** Abstract nullary functor in a @ref CallQueue. Custom implementations may derive from this object for efficiency instead of using the automatic binding functions. */ class Work : public LockFreeQueue ::Node, public AllocatedBy { public: virtual ~Work () { } /** Calls the functor. This executes during the queue's call to synchronize(). */ virtual void operator () () = 0; }; //============================================================================ /** Create the CallQueue. The queue starts out open and empty. @param name A string to identify the queue during debugging. */ explicit CallQueue (String name); /** Destroy the CallQueue. @invariant Destroying a queue that contains functors results in undefined behavior. @note It is customary to call close() on the CallQueue early in the shutdown process to catch functors going into the queue late. */ virtual ~CallQueue (); //============================================================================ /** Add a functor and possibly synchronize. Use this when you want to perform the bind yourself. @param f The functor to add, typically the return value of a call to bind(). @see call */ template void callf (Functor f) { callp (new (m_allocator) CallType (f)); } /** Add a function call and possibly synchronize. Parameters are evaluated immediately and added to the queue as a packaged functor. If the current thread of execution is the same as the thread associated with the CallQueue, synchronize() is called automatically. This behavior can be avoided by using queue() instead. @param f The function to call followed by up to eight parameters, evaluated immediately. The parameter list must match the function signature. For class member functions, the first argument must be a pointer to the class object. @see queue @todo Provide an example of when synchronize() is needed in call(). */ /** @{ */ #if BEAST_VARIADIC_MAX >= 1 template void call (Fn f) { callf (functional::bind (f)); } #endif #if BEAST_VARIADIC_MAX >= 2 template void call (Fn f, T1 t1) { callf (functional::bind (f, t1)); } #endif #if BEAST_VARIADIC_MAX >= 3 template void call (Fn f, T1 t1, T2 t2) { callf (functional::bind (f, t1, t2)); } #endif #if BEAST_VARIADIC_MAX >= 4 template void call (Fn f, T1 t1, T2 t2, T3 t3) { callf (functional::bind (f, t1, t2, t3)); } #endif #if BEAST_VARIADIC_MAX >= 5 template void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4) { callf (functional::bind (f, t1, t2, t3, t4)); } #endif #if BEAST_VARIADIC_MAX >= 6 template void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { callf (functional::bind (f, t1, t2, t3, t4, t5)); } #endif #if BEAST_VARIADIC_MAX >= 7 template void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { callf (functional::bind (f, t1, t2, t3, t4, t5, t6)); } #endif #if BEAST_VARIADIC_MAX >= 8 template void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { callf (functional::bind (f, t1, t2, t3, t4, t5, t6, t7)); } #endif #if BEAST_VARIADIC_MAX >= 9 template void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { callf (functional::bind (f, t1, t2, t3, t4, t5, t6, t7, t8)); } #endif /** @} */ /** Add a functor without synchronizing. Use this when you want to perform the bind yourself. @param f The functor to add, typically the return value of a call to bind(). @see queue */ template void queuef (Functor f) { queuep (new (m_allocator) CallType (f)); } /** Add a function call without synchronizing. Parameters are evaluated immediately, then the resulting functor is added to the queue. This is used to postpone the call to synchronize() when there would be adverse side effects to executing the function immediately. In this example, we use queue() instead of call() to avoid a deadlock: @code struct SharedState; // contains data shared between threads SharedData sharedState; void stateChanged () { SharedData ::ReadAccess state (sharedState); // (read state) } CallQueue fifo; void changeState () { SharedData ::WriteAccess state (sharedState); // (read and write state) fifo.call (&stateChanged); // BUG: DEADLOCK because of the implicit synchronize(). fifo.queue (&stateChanged); // Okay, synchronize() will be called later, // after the write lock is released. } @endcode @param f The function to call followed by up to eight parameters, evaluated immediately. The parameter list must match the function signature. For non-static class member functions, the first argument must be a pointer an instance of the class. @see call */ /** @{ */ #if BEAST_VARIADIC_MAX >= 1 template void queue (Fn f) { queuef (functional::bind (f)); } #endif #if BEAST_VARIADIC_MAX >= 2 template void queue (Fn f, T1 t1) { queuef (functional::bind (f, t1)); } #endif #if BEAST_VARIADIC_MAX >= 3 template void queue (Fn f, T1 t1, T2 t2) { queuef (functional::bind (f, t1, t2)); } #endif #if BEAST_VARIADIC_MAX >= 4 template void queue (Fn f, T1 t1, T2 t2, T3 t3) { queuef (functional::bind (f, t1, t2, t3)); } #endif #if BEAST_VARIADIC_MAX >= 5 template void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4) { queuef (functional::bind (f, t1, t2, t3, t4)); } #endif #if BEAST_VARIADIC_MAX >= 6 template void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { queuef (functional::bind (f, t1, t2, t3, t4, t5)); } #endif #if BEAST_VARIADIC_MAX >= 7 template void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { queuef (functional::bind (f, t1, t2, t3, t4, t5, t6)); } #endif #if BEAST_VARIADIC_MAX >= 8 template void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { queuef (functional::bind (f, t1, t2, t3, t4, t5, t6, t7)); } #endif #if BEAST_VARIADIC_MAX >= 9 template void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { queuef (functional::bind (f, t1, t2, t3, t4, t5, t6, t7, t8)); } #endif /** @} */ protected: //============================================================================ /** Synchronize the queue. A synchronize operation calls all functors in the queue. If a functor causes additional functors to be added, they are eventually executed before synchronize() returns. Derived class call this when the queue is signaled, and optionally at any other time. Calling this function from more than one thread simultaneously is undefined. @return true if any functors were executed. */ bool synchronize (); /** Close the queue. Functors may not be added after this routine is called. This is used for diagnostics, to track down spurious calls during application shutdown or exit. Derived classes may call this if the appropriate time is known. The queue is synchronized after it is closed. */ void close (); /** Called when the queue becomes signaled. A queue is signaled on the transition from empty to non-empty. Derived classes implement this function to perform a notification so that synchronize() will be called. For example, by triggering a WaitableEvent. @note Due to the implementation the queue can remain signaled for one extra cycle. This does not happen under load and is not an issue in practice. */ virtual void signal () = 0; /** Called when the queue is reset. A queue is reset when it was previously signaled and then becomes empty as a result of a call to synchronize. */ virtual void reset () = 0; public: //============================================================================ /** Add a raw call. @internal Custom implementations use this to control the allocation. @param c The call to add. The memory must come from the allocator. */ void callp (Work* c); /** Queue a raw call. Custom implementations use this to control the allocation. @param c The call to add. The memory must come from the allocator. */ void queuep (Work* c); /** Retrieve the allocator. @return The allocator to use when allocating a raw Work object. */ inline AllocatorType& getAllocator () { return m_allocator; } /** See if the caller is on the association thread. @return `true` if the calling thread of execution is associated with the queue. */ bool isAssociatedWithCurrentThread () const; /** See if the queue is being synchronized. This is used for diagnostics. @note This must be called from the associated thread or else the return value is undefined. @return `true` if the call stack contains synchronize() for this queue. */ bool isBeingSynchronized () const { return m_isBeingSynchronized.isSignaled (); } private: template class CallType : public Work { public: explicit CallType (Functor f) : m_f (f) { } void operator () () { m_f (); } private: Functor m_f; }; bool doSynchronize (); private: String const m_name; Thread::ThreadID m_id; LockFreeQueue m_queue; AtomicFlag m_closed; AtomicFlag m_isBeingSynchronized; AllocatorType m_allocator; }; #endif