All SObjectizer's dipatchers use two kinds of event queues: multi-producer/single-consumer and multi-producer/multi-consumer queues. Those queues require synchronization for protection of queues' data from access from different threads. Synchronization objects are necessary not only for data protection but also for notification of consumers about appearance of new events (a customer sleeps when event queue is empty and must be awakened on arrival of new event). Until v.5.5.10 combined locking scheme with spinlock and mutex/condition_variable was used. This locking scheme works as described below.
There is a busy waiting part:
std::this_thread::yield
method.Busy waiting works great when message passing is intensive. But if there are some pauses in generation of events then locking scheme switches to usage of mutex and condition variable:
There is a time limit for busy waiting. If there is no new events during some time then consumer thread switches from busy waiting on spinlock to ordinary waiting on mutex/condition_variable. This time limit was one millisecond.
This scheme was implemented as combined_lock abstraction and was used in all dispatchers util v.5.5.10.
Locking with combined_lock is efficient if application is working under the heavy load. But not effiecient on some specific load profiles.
Lets imagine active agent which initiates several periodic messages. All actions are performed by that agent only on arrival of periodic messages. All other time agent do nothing and its working thread is sleeping on empty event queue.
If an application uses just one such active agent the overhead cost of busy waiting is relative small and could be ignored. But if there are several dozens of such agents the overhead cost could be relative high: 3-4% of CPU usage even if application do nothing and all working threads just do busy waiting periodically. Situation could be more dramatic if there are several such application on the same server.
To solve this problem v.5.5.10 introduces concept of lock factoris for MPSC queues and v.5.5.11 expands this concept for MPMC queues. An user can specify lock factory during the creation of a dispatcher. Dispatcher will use a lock created by that factory.
There are two lock factories:
New factories can be added in the future versions of SObjectizer.
combined_lock_factory is still used by default. If this locking scheme is not appropriate for your application it is possible to specify different locking factory (or to specify combined_lock_factory with different busy waiting time).
To specify lock factory it is necessary to use disp_params_t
object and the corresponding create_disp
or create_private_disp
functions for dispatcher creation. Since v.5.5.11 there are appropriate definitions of disp_params_t
types is the dispatcher's namespaces.
There are also namespaces queue_traits
with definitions of lock factory functions and other queue-related stuff in dispatchers' namespaces (like so_5::disp::one_thread::queue_traits
or so_5::disp::prio_one_thread::strictly_ordered::queue_traits
). Technicaly speaking those queue_traits
namespaces are just an alias for so_5::disp::mpsc_queue_traits
or so_5::disp::mpmc_queue_traits
namespace.
Because of that the preparation of disp_params_t
for a dispatcher look similar for different dispatcher types. For example that is for so_5::disp::one_thread
dispatcher:
using namespace so_5::disp::one_thread; auto disp = create_private_disp( env, "handler", disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) );
And this is for so_5::disp::active_obj
dispatcher:
using namespace so_5::disp::active_obj; auto disp = create_private_disp( env, "handlers", disp_params_t{}.tune_queue_params( []( queue_traits::disp_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) );
And this is for so_5::disp::thread_pool
dispatcher:
using namespace so_5::disp::thread_pool; auto disp = create_private_disp( env, "handlers", disp_params_t{} .thread_count(16) .set_queue_params( queue_traits::queue_params_t{} .lock_factory( queue_traits::simple_lock_factory() ) ) );
Please note that lock_factory can be specified only at the moment of the creation of a dispatcher. Lock cannot be changed after the creation of a dispatcher.
Lock factory can be specified for the default dispatcher too. That dispatcher is created automatically by SObjectizer Environment. To specify lock factory for the default dispacher it is necessary to use so_5::rt::environment_params_t
:
so_5::launch( []( so_5::rt::environment_t & env ) {...}, []( so_5::rt::environment_params_t & env_params ) { using namespace so_5::disp::one_thread; env_params.default_disp_params( disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { queue_params.lock_factory( queue_traits::simple_lock_factory() ); } ) ); } );
At mentioned above the combined_lock_factory is still used by default. Default waiting time for busy waiting is specified by default_combined_lock_waiting_time()
function. In v.5.5.10 it is one millisecond. It is possible to set different waiting time by using combined_lock_factory(duration)
function:
using namespace so_5::disp::active_group; auto disp = create_private_disp( env, "handlers", disp_params_t{}.tune_queue_params( []( queue_traits::queue_params_t & queue_params ) { // Set up combined_lock with 0.5 second busy waiting time. queue_params.lock_factory( queue_traits::combined_lock_factory( std::chrono::milliseconds(500) ) ); } ) );
News: 2015/11/sobjectizer-v5511-released
Wiki: so-5.5 In-depth - Dispatchers