2. SObjectizer is a small framework that simplifies
the development of concurrent and event-driven
applications in C++.
SObjectizer Team, Jan 2016
3. SObjectizer was designed under the influence of
several approaches. The Actor Model* is one of
them. But the history of SObjectizer started long
before this model became widely known.
*
http://en.wikipedia.org/wiki/Actor_model SObjectizer Team, Jan 2016
4. SObjectizer’s main ideas and principles were
formulated in the middle of 1990s, during the
development of SCADA Objectizer project in
Development Bureau of System Programming in
Homyel, Belarus (1996-2000).
SCADA Objectizer’s ideas were reused in the new
project SObjectizer-4 in 2002.
SObjectizer Team, Jan 2016
5. Evolution of SObjectizer-4 was stopped in 2010
and the development of SObjectizer-5 started.
SObjectizer Team, Jan 2016
6. SObjectizer was an in-house project of
JSC Intervale*
for the long time.
It was used in the development of the following
software systems:
● SMS/USSD traffic service
● financial transaction handling
● software parameters monitoring.
*
www.intervale.ru SObjectizer Team, Jan 2016
7. SObjectizer was published as an OpenSource
project on SourceForge under 3-clauses BSD-
licence in 2006.
Since 2013 SObjectizer’s development completely
moved to SourceForge.
SObjectizer now is an independent project which is
totally separated from JSC Intervale.
SObjectizer Team, Jan 2016
8. SObjectizer allows to build a concurrent
application as a set of agent-objects...
...which interact with each other only by means of
asynchronous messages.
SObjectizer Team, Jan 2016
9. Every agent receives its own working context.
This context is used for message processing.
An agent is bound to its context. It allows not to
worry about defense of integrity of the agent’s
data in the multithreaded environment.
This defense is automatically performed by
SObjectizer itself!
SObjectizer Team, Jan 2016
11. All SObjectizer’s work is performed inside
SObjectizer Environment.
SObjectizer Environment is a container for
SObjectizer Run-Time, agent’s cooperations,
message boxes, dispatchers and timer thread.
It is possible to create several Environments in one
application. Each Environment will work
independently from others.
SObjectizer Team, Jan 2016
12. SObjectizer Environment is created by
so_5::launch() function.
A new instance of Environment is created and started
inside so_5::launch(). The control from so_5::launch()
is returned only when Environment finished its
execution.
What happened inside Environment is completely
dependent on user supplied starting function.
SObjectizer Team, Jan 2016
13. It looks like that:
#include <iostream>
#include <so_5/all.hpp>
void init( so_5::environment_t & env ) { ... }
int main()
{
try
{
so_5::launch( &init );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
SObjectizer Team, Jan 2016
14. #include <iostream>
#include <so_5/all.hpp>
void init( so_5::environment_t & env ) { ... }
int main()
{
try
{
so_5::launch( &init );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
Main header file with all
necessary definitions.
It looks like that:
SObjectizer Team, Jan 2016
15. #include <iostream>
#include <so_5/all.hpp>
void init( so_5::environment_t & env ) { ... }
int main()
{
try
{
so_5::launch( &init );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
User supplied starting function.
It creates all application specific
stuff.
It looks like that:
SObjectizer Team, Jan 2016
16. #include <iostream>
#include <so_5/all.hpp>
void init( so_5::environment_t & env ) { ... }
int main()
{
try
{
so_5::launch( &init );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
SObjectizer Environment object. Starting
function will work inside it.
It looks like that:
SObjectizer Team, Jan 2016
17. #include <iostream>
#include <so_5/all.hpp>
void init( so_5::environment_t & env ) { ... }
int main()
{
try
{
so_5::launch( &init );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
Creation of Environment, starting
it and invoking init() inside that
Environment.
Control will be returned when all
application-specific agents finish
their work.
It looks like that:
SObjectizer Team, Jan 2016
18. #include <iostream>
#include <so_5/all.hpp>
void init( so_5::environment_t & env ) { ... }
int main()
{
try
{
so_5::launch( &init );
}
catch( const std::exception & x )
{
std::cerr << "Exception: " << x.what() << std::endl;
}
}
Error handling. Error reporting is done via
exceptions.
It looks like that:
SObjectizer Team, Jan 2016
19. Usually one or more agent’s cooperations are
created inside the starting function.
Cooperation is a group of agents which must work
together and can’t exist one without another.
For example: pinger and ponger agents which send
ping/pong messages back and forth. There is no any
sense in pinger agent without ponger agent. They
must appear and disappear at the same time.
SObjectizer Team, Jan 2016
20. Creation of a cooperation with two agents:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
env.register_coop( std::move( coop ) );
}
SObjectizer Team, Jan 2016
21. void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
env.register_coop( std::move( coop ) );
}
Creation consists of three steps.
At the beginning the Environment
creates a cooperation...
Creation of a cooperation with two agents:
SObjectizer Team, Jan 2016
22. void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
env.register_coop( std::move( coop ) );
}
Then the cooperation is filled
up with agents...
make_agent() method is like
std::make_unique from
C++14. It creates dynamically
allocated agent of specified
type.
Creation of a cooperation with two agents:
SObjectizer Team, Jan 2016
23. void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
env.register_coop( std::move( coop ) );
}
Then the cooperation
is being registered.
Creation of a cooperation with two agents:
SObjectizer Team, Jan 2016
24. void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
env.register_coop( std::move( coop ) );
}
Every cooperation must have unique
name. The uniqueness is checked
inside register_coop().
But SObjectizer can be asked to
generate a name for the new
cooperation automatically.
Creation of a cooperation with two agents:
SObjectizer Team, Jan 2016
25. void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
env.register_coop( std::move( coop ) );
}
Starting function finishes after cooperation registration.
But the Environment will work until that cooperation will be deregistered.
Or the Environment stop function will be called explicitly.
Creation of a cooperation with two agents:
SObjectizer Team, Jan 2016
26. What is an agent?
Let’s start with the simplest agent in this example.
A ponger agent.
SObjectizer Team, Jan 2016
27. It does very simple tasks:
● receives ping messages
● replies with pong messages.
So first of all let’s define those messages...
SObjectizer Team, Jan 2016
28. Definition of the messages:
struct ping : public so_5::message_t
{
unsigned int m_req;
ping( unsigned int req ) : m_req{ req } {}
};
struct pong : public so_5::message_t
{
unsigned int m_resp;
pong( unsigned int resp ) : m_resp{ resp } {}
};
SObjectizer Team, Jan 2016
29. struct ping : public so_5::message_t
{
unsigned int m_req;
ping( unsigned int req ) : m_req{ req } {}
};
struct pong : public so_5::message_t
{
unsigned int m_resp;
pong( unsigned int resp ) : m_resp{ resp } {}
};
Every message must be
represented by a separate C++
class (or a structure).
Message dispatching and selection
of a handler are based on the
message type information.
Definition of the messages:
SObjectizer Team, Jan 2016
30. struct ping : public so_5::message_t
{
unsigned int m_req;
ping( unsigned int req ) : m_req{ req } {}
};
struct pong : public so_5::message_t
{
unsigned int m_resp;
pong( unsigned int resp ) : m_resp{ resp } {}
};
Messages types with some data
inside usually derived from common
base class so_5::message_t. In v.
5.5.15 it is not necessary but
inheritance is shown here just for
demonstration purposes.
There are also messages without
actual data. They are called signals.
They will be described further.
Definition of the messages:
SObjectizer Team, Jan 2016
31. struct ping : public so_5::message_t
{
unsigned int m_req;
ping( unsigned int req ) : m_req{ req } {}
};
struct pong : public so_5::message_t
{
unsigned int m_resp;
pong( unsigned int resp ) : m_resp{ resp } {}
};
SObjectizer has no significant
limits for message’s content.
In this particular case the m_req
and m_resp fields are
necessary for the sample logic.
They have no relation to
SObjectizer features.
Definition of the messages:
SObjectizer Team, Jan 2016
33. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
Every ordinary agent must be
represented by a C++ class.
There also can be non ordinary
agents named ad-hoc agents.
They will be described further.
ponger agent:
SObjectizer Team, Jan 2016
34. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
Class of ordinary agent must be
derived from common base type
so_5::agent_t.
ponger agent:
SObjectizer Team, Jan 2016
35. ponger agent:
class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
Every agent must be bound to
an Environment where an
agent will work.
Because of that a reference to
Environment object must be
passed to the agent’s
constructor and redirected to
the constructor of the base
type so_5::agent_t. A
reference to Environment is
inside of context_t object.
SObjectizer Team, Jan 2016
36. ponger agent:
class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
SObjectizer calls
so_define_agent() method
before an agent will be
registered.
Agent must perform all
necessary tuning actions in
this method. Create
message subscriptions in
particular.
SObjectizer Team, Jan 2016
37. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
Ponger agent subscribes only to
one message of ping type. This
message is going from a message
box with name “table”.
Message box must be specified
explicitly. But the message type is
deduced by SObjectizer
automatically from the message
handler signature.
ponger agent:
SObjectizer Team, Jan 2016
38. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
A message handler method is named
event handler method. Or just event.
ponger agent:
SObjectizer Team, Jan 2016
39. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
A message instance which
caused an event is passed to
event handler by const reference.
Event handler must not modify
that message instance because it
can be handled by the different
agents at the same time.
ponger agent:
SObjectizer Team, Jan 2016
40. Agent replies by sending a pong
message instance to the message
box with name “table”.
ponger agent:
class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
SObjectizer Team, Jan 2016
41. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
so_5::send() template function
constructs an object of pong type
and sends it to the message box
specified.
ponger agent:
SObjectizer Team, Jan 2016
42. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
All arguments after message box
are passed to the message object’
s constructor.
In this case it is resp argument for
the pong’s constructor.
ponger agent:
SObjectizer Team, Jan 2016
43. class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &ponger::evt_ping );
}
private :
const so_5::mbox_t m_table;
void evt_ping( const ping & evt )
{
so_5::send< pong >( m_table, evt.m_req );
}
};
Ponger agent gets a reference to
the message box for message
exchange by itself.
In this case it is a message box
with name “table”. This box is
created by invocation of
create_mbox(). A reference to
message box is stored inside the
agent.
ponger agent:
SObjectizer Team, Jan 2016
45. class pinger : public so_5::agent_t
{
public :
pinger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &pinger::evt_pong );
}
virtual void so_evt_start() override
{
so_5::send< ping >( m_table, 500 );
}
Pinger agent is very similar to the
ponger agent: receives a
context_t object in its constructor
and creates a reference to
message box with name “table”.
Pinger also subscribes to a single
message in so_define_agent().
pinger agent (beginning):
SObjectizer Team, Jan 2016
46. class pinger : public so_5::agent_t
{
public :
pinger( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override
{
so_subscribe( m_table ).event( &pinger::evt_pong );
}
virtual void so_evt_start() override
{
so_5::send< ping >( m_table, 500 );
}
But there is one significant
distinction: so_evt_start()
method. This method is called
just after successful registration
of the agent’s cooperation. An
agent can perform any starting
actions in that method.
In this particular case: sends the
very first ping message.
pinger agent (beginning):
SObjectizer Team, Jan 2016
48. pinger agent (ending):
private :
const so_5::mbox_t m_table;
void evt_pong( const pong & evt )
{
if( evt.m_resp )
so_5::send< ping >( m_table, evt.m_resp - 1 );
else
so_deregister_agent_coop_normally();
}
};
In the evt_pong() the agent can
continue the message exchange
by sending the next ping
message. Or, in the case when all
messages have been sent,
cooperation deregistration can be
initiated.
SObjectizer Team, Jan 2016
49. pinger agent (ending):
private :
const so_5::mbox_t m_table;
void evt_pong( const pong & evt )
{
if( evt.m_resp )
so_5::send< ping >( m_table, evt.m_resp - 1 );
else
so_deregister_agent_coop_normally();
}
};
The reasons for deregistering a
cooperation might be different. In this
case the deregistration is a normal part
of the application logic.
There will be no live cooperations after
deregistration of pinger/ponger
cooperation. So Environment will finish
its work and so_5::launch will return
control to the caller.
SObjectizer Team, Jan 2016
50. Few words about message boxes (mboxes)...
Despite other similar tools like Erlang, Akka or CAF,
in SObjectizer a message is sent not to an agent, but
to a message box (mbox).
There could be one agent behind the mbox.
Or many agents.
Or none.
SObjectizer Team, Jan 2016
51. There are two types of mboxes in SObjectizer:
SObjectizer Team, Jan 2016
52. Multi-Producers/Multi-Consumers mboxes.
They are like “bulletin boards”. A message sent to
such mbox becomes available for all subscribers
of the mbox. MPMC-mbox is shown in the code
above.
SObjectizer Team, Jan 2016
54. Lets add another agent to the example to show
specifics of MPMC-mboxes...
This agent will listen the message exchange
between pinger and ponger agents, and will count
the messages sent.
SObjectizer Team, Jan 2016
56. listener agent:
class listener : public so_5::agent_t
{
public :
listener( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override {
so_subscribe( m_table )
.event( [this]( const ping & ) { ++m_pings; } )
.event( [this]( const pong & ) { ++m_pongs; } );
}
virtual void so_evt_finish() override {
std::cout << "result: " << m_pings << "/" << m_pongs << std::endl;
}
private :
const so_5::mbox_t m_table;
unsigned int m_pings = 0;
unsigned int m_pongs = 0;
};
It is necessary to receive two
messages. Because of that the
agent subscribes two events.
Lambda-functions are used
instead of event handler
methods. Types of messages
are automatically deduced from
lambdas signatures.
SObjectizer Team, Jan 2016
57. listener agent:
class listener : public so_5::agent_t
{
public :
listener( context_t ctx )
: so_5::agent_t( ctx )
, m_table( env.create_mbox( "table" ) )
{}
virtual void so_define_agent() override {
so_subscribe( m_table )
.event( [this]( const ping & ) { ++m_pings; } )
.event( [this]( const pong & ) { ++m_pongs; } );
}
virtual void so_evt_finish() override {
std::cout << "result: " << m_pings << "/" << m_pongs << std::endl;
}
private :
const so_5::mbox_t m_table;
unsigned int m_pings = 0;
unsigned int m_pongs = 0;
};
so_evt_finish() method is the
opposite to so_evt_start(). It is
called for agent just before the very
end of agent’s work. In this case
that method is used for result
printing.
SObjectizer Team, Jan 2016
58. If we add a listener to the cooperation:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
coop->make_agent< pinger >();
coop->make_agent< ponger >();
coop->make_agent< listener >();
env.register_coop( std::move( coop ) );
}
SObjectizer Team, Jan 2016
59. Then we will see at the end:
result: 501/501
It means that message sending to MPMC-mbox is
a broadcast message sending.
SObjectizer Team, Jan 2016
60. MPMC-mboxes must be created manually.
MPSC-mboxes, in contradiction, are created
automatically for every agent.
It means that every agent has its own MPSC-mbox.
In SObjectizer's terms such mbox is often named
direct_mbox.
SObjectizer Team, Jan 2016
61. When a message is sent to MPSC-mbox it will go
to mbox owner for processing. Or just discarded if
mbox owner is not subscribed to that message.
It means that if two agents interact via
direct_mboxes nobody can listen them.
SObjectizer Team, Jan 2016
62. But direct_mboxes are used not for creation of a
“private channels” for message exchanges.
Direct_mboxes are more efficient than MPMC-
mboxes. Dispatching for direct_mboxes is simpler
and requires fewer internal locks.
Therefore usage of direct_mboxes is more preferable
if application logic doesn’t require broadcast message
sending.
SObjectizer Team, Jan 2016
63. There is no need in broadcast message sending
in the example above.
Lets rewrite it with direct_mboxes.
And throw out a listener agent. Lets pinger and
ponger agent count the messages by themselves.
And replace messages with signals.
SObjectizer Team, Jan 2016
64. A signal is a special case of a message when
only the fact of the message existence is
important.
But the message itself has no any data inside.
It is like sending of atoms in Erlang, when only an
atom is sent without any additional data.
SObjectizer Team, Jan 2016
65. In the application code written with SObjectizer
signals are so widely used that SObjectizer’s API
was extended to simplify usage of signals.
At the API level the work with signal is very similar
to the work with messages. Sometimes.
Sometimes not.
SObjectizer Team, Jan 2016
66. Replace ping and pong messages with signals...
SObjectizer Team, Jan 2016
67. Replace ping and pong messages with signals...
SObjectizer Team, Jan 2016
struct ping : public so_5::message_t
{
unsigned int m_req;
ping( unsigned int req ) : m_req{ req } {}
};
struct pong : public so_5::message_t
{
unsigned int m_resp;
pong( unsigned int resp ) : m_resp{ resp } {}
};
struct ping : public so_5::signal_t {};
struct pong : public so_5::signal_t {};
68. Replace ping and pong messages with signals...
SObjectizer Team, Jan 2016
struct ping : public so_5::message_t
{
unsigned int m_req;
ping( unsigned int req ) : m_req{ req } {}
};
struct pong : public so_5::message_t
{
unsigned int m_resp;
pong( unsigned int resp ) : m_resp{ resp } {}
};
struct ping : public so_5::signal_t {};
struct pong : public so_5::signal_t {};
Signal types must be derived from so_5::signal_t
and must not contain any data inside.
70. Changing of pinger agent (beginning):
class pinger : public so_5::agent_t
{
public :
pinger( context_t ctx )
: so_5::agent_t( ctx )
{}
void set_ponger_mbox( const so_5::mbox_t & mbox ) {
m_ponger = mbox;
}
virtual void so_define_agent() override {
so_subscribe_self().event< pong >(
[this]{
++m_pongs;
so_5::send< ping >( m_ponger );
} );
}
A direct_mbox can be accessed
only after the agent creation.
Because of that a separate method
is needed for connecting of pinger
and ponger agents. It will be called
after instantiation of the agent.
SObjectizer Team, Jan 2016
71. Changing of pinger agent (beginning):
class pinger : public so_5::agent_t
{
public :
pinger( context_t ctx )
: so_5::agent_t( ctx )
{}
void set_ponger_mbox( const so_5::mbox_t & mbox ) {
m_ponger = mbox;
}
virtual void so_define_agent() override {
so_subscribe_self().event< pong >(
[this]{
++m_pongs;
so_5::send< ping >( m_ponger );
} );
}
so_subscribe_self() method is used
for subscription to message from
agent's direct_mbox.
Method event receives a lambda-
function with reaction to pong signal.
SObjectizer Team, Jan 2016
72. Changing of pinger agent (beginning):
class pinger : public so_5::agent_t
{
public :
pinger( context_t ctx )
: so_5::agent_t( ctx )
{}
void set_ponger_mbox( const so_5::mbox_t & mbox ) {
m_ponger = mbox;
}
virtual void so_define_agent() override {
so_subscribe_self().event< pong >(
[this]{
++m_pongs;
so_5::send< ping >( m_ponger );
} );
}
The signal type must be explicitly
specified during subscription. A
method or lambda-function without
argument can only be used as
signal handler. Unlike a message
there is no a signal instance to be
passed to a handler. Because of
that there is no way to deduce
signal type by the event handler’s
signature.
SObjectizer Team, Jan 2016
74. Changing of pinger agent (ending):
virtual void so_evt_start() override {
so_5::send< ping >( m_ponger );
}
virtual void so_evt_finish() override {
std::cout << "pongs: " << m_pongs << std::endl;
}
private :
so_5::mbox_t m_ponger;
unsigned int m_pongs = 0;
};
Signal sending is performed by the
same so_5::send() function. Like a
message sending. But there is no
any other arguments after receiver
mbox.
SObjectizer Team, Jan 2016
75. Similar changes to a ponger agent (beginning):
class ponger : public so_5::agent_t
{
public :
ponger( context_t ctx )
: so_5::agent_t( ctx)
{}
void set_pinger_mbox( const so_5::mbox_t & mbox ) {
m_pinger = mbox;
}
virtual void so_define_agent() override {
so_subscribe_self().event< ping >(
[this]{
++m_pings;
so_5::send< pong >( m_pinger );
} );
}
SObjectizer Team, Jan 2016
76. Similar changes to a ponger agent (ending):
virtual void so_evt_finish() override {
std::cout << "pings: " << m_pings << std::endl;
}
private :
so_5::mbox_t m_pinger;
unsigned int m_pings = 0;
};
SObjectizer Team, Jan 2016
77. Creation of the cooperation becomes more verbose:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
auto a_pinger = coop->make_agent< pinger >();
auto a_ponger = coop->make_agent< ponger >();
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
env.register_coop( std::move( coop ) );
}
SObjectizer Team, Jan 2016
79. No one stops them!
They will ping each other infinitely.
SObjectizer Team, Jan 2016
80. Fix this problem by adding another agent that will
stop example after one second...
SObjectizer Team, Jan 2016
81. Because this agent will handle only one event
there is no need to define a separate class for it,
redefine so_define_agent() method and so on...
An ad-hoc agent will be used instead.
SObjectizer Team, Jan 2016
82. Ad-hoc agent for stopping the work after one
second:
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
SObjectizer Team, Jan 2016
83. Ad-hoc agent for stopping the work after one
second:
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
Stop signal.
SObjectizer Team, Jan 2016
84. Ad-hoc agent for stopping the work after one
second:
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
Ad-hoc agent creation.
A handle is returned. This handle
can be used for agent tuning.
SObjectizer Team, Jan 2016
85. Ad-hoc agent for stopping the work after one
second:
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
The new agent is subscribed to just
one signal.
SObjectizer Team, Jan 2016
86. Ad-hoc agent for stopping the work after one
second:
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
Signal is expected from direct_mbox
of the the agent.
SObjectizer Team, Jan 2016
87. Ad-hoc agent for stopping the work after one
second:
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
The signal handler will stop
SObjectizer Environment.
The single cooperation will be
deregistered automatically.
SObjectizer Team, Jan 2016
88. Ad-hoc agent is created and tuned.
A delayed for one second stop signal must be sent:
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( stopper, std::chrono::seconds(1) );
SObjectizer Team, Jan 2016
89. Ad-hoc agent is created and tuned.
A delayed for one second stop signal must be sent:
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( stopper, std::chrono::seconds(1) );
A so_5::send_delayed function
make delayed send of a signal or a
message.
In this case a stop signal will be
sent to the direct_mbox of the new
ad-hoc agent with one second
delay.
SObjectizer Team, Jan 2016
90. Finally the starting function becomes:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname );
auto a_pinger = coop->make_agent< pinger >();
auto a_ponger = coop->make_agent< ponger >();
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent();
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( stopper, std::chrono::seconds(1) );
}
SObjectizer Team, Jan 2016
91. Running the updated example...
pongs: 4441168
pings: 4441169
SObjectizer Team, Jan 2016
92. Just above 8M messages per seconds.
Core i7 2.4GHz, 8GiB RAM, Win8.1 64-bit,
Visual C++ 13.0 64-bit.
SObjectizer Team, Jan 2016
93. So far, so good. But which working context has
been used?
SObjectizer Team, Jan 2016
94. All agents were working on a single thread!
This example just shows the effectiveness of
message passing between agents which are
working on the same context.
But who choose the context?
And how an agent can be bound to another
context?
SObjectizer Team, Jan 2016
95. A programmer makes choice of context when
binds an agent to a dispatcher.
If dispatcher is not specified then an agent will be
bound to default dispatcher.
Just like in the example above.
SObjectizer Team, Jan 2016
96. The default dispatcher runs all the agents on a
single working thread.
There is a something like “cooperative
multitasking” for agents on the default dispatcher.
If one of them will slow down the other will slow
down too.
But it is possible to create any number of various
dispatchers and bind agents to the them.
SObjectizer Team, Jan 2016
97. Lets bind pinger and ponger agent to different
working threads (every agent will have its own
dedicated working thread)...
An instance of active_obj dispatcher will be
created for that. Agents will be bound to it.
This dispatcher creates a dedicated thread for
each agent bound to it (an agent becomes an
active object).
SObjectizer Team, Jan 2016
98. There is nothing to change inside the agents...
All the changes will be in the starting function
only.
SObjectizer Team, Jan 2016
99. Binding of the agents to different dispatches:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname,
so_5::disp::active_obj::create_private_disp( env )->binder() );
auto a_pinger = coop->make_agent< pinger >();
auto a_ponger = coop->make_agent< ponger >();
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent( so_5::create_default_disp_binder() );
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) );
}
SObjectizer Team, Jan 2016
100. Binding of the agents to different dispatches:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname,
so_5::disp::active_obj::create_private_disp( env )->binder() );
auto a_pinger = coop->make_agent< pinger >();
auto a_ponger = coop->make_agent< ponger >();
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent( so_5::create_default_disp_binder() );
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) );
}
Creation of the active objects
dispatcher. A binder for that
dispatcher will be used as
main dispatcher binder for the
new coop.
SObjectizer Team, Jan 2016
101. Binding of the agents to different dispatches:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname,
so_5::disp::active_obj::create_private_disp( env )->binder() );
auto a_pinger = coop->make_agent< pinger >();
auto a_ponger = coop->make_agent< ponger >();
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent( so_5::create_default_disp_binder() );
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) );
}
Pinger and ponger agents
are being added to the
cooperation without any
additional information. It
means they will be bound to
the main cooperation
dispatcher. It is the
active_obj dispatcher created
above.
SObjectizer Team, Jan 2016
102. Binding of the agents to different dispatches:
void init( so_5::environment_t & env )
{
auto coop = env.create_coop( so_5::autoname,
so_5::disp::active_obj::create_private_disp( env )->binder() );
auto a_pinger = coop->make_agent< pinger >();
auto a_ponger = coop->make_agent< ponger >();
a_pinger->set_ponger_mbox( a_ponger->so_direct_mbox() );
a_ponger->set_pinger_mbox( a_pinger->so_direct_mbox() );
struct stop : public so_5::signal_t {};
auto stopper = coop->define_agent( so_5::create_default_disp_binder() );
stopper.event< stop >( stopper, [&env]{ env.stop(); } );
env.register_coop( std::move( coop ) );
so_5::send_delayed< stop >( env, stopper, std::chrono::seconds(1) );
}
But there is no need for a separate thread
for stopper agent. Because of that this
agent is being bound to the default
dispatcher explicitly. Without this the
stopper will be bound to the cooperation
main dispatcher.
SObjectizer Team, Jan 2016
106. Yes. It is.
And there could be more strange results.
But what happened?
SObjectizer Team, Jan 2016
107. Pinger and ponger agents are now working on
different threads and compete for std::cout object.
As result the output to std::cout got mixed. It could
be even worse. Or could not be mixed at all.
This is concurrency in action...
SObjectizer Team, Jan 2016
109. The performance has dropped. We have seen 8M
messages per second on one thread. And just 2M
on two threads.
It is expected result. Passing a single message
from thread to thread is an expensive operation.
SObjectizer Team, Jan 2016
110. But is there any changes in the agents?
SObjectizer Team, Jan 2016
113. It is a direct consequence of interaction only by
asynchronous messages.
Because of that agents are unaware about
working context.
Providing an appropriate set of different
dispatchers is a task of SObjectizer.
SObjectizer Team, Jan 2016
114. SObjectizer has several ready-to-use dispatchers.
There are one_thread, active_obj, active_group,
thread_pool, adv_thread_pool and three more
dispatchers which support priorities of agents...
SObjectizer Team, Jan 2016
115. A programmer can not only select the appropriate
type of a dispatcher...
...but can create any number of those dispatchers.
SObjectizer Team, Jan 2016
116. For example:
● one one_thread dispatcher for AMQP-client agent;
● one thread_pool dispatcher for handling requests from
AMQP-queues;
● one active_obj dispatcher for DBMS-related agents;
● yet another active_obj dispatcher for agents whose work
with HSMs connected to the computer;
● and yet another thread_pool dispatcher for agents for
managing all the stuff described above.
SObjectizer Team, Jan 2016
117. But there are yet more SObjectizer features...
Such important things like:
● agent’s states
● periodic messages
● synchronous agents interaction
● child cooperation
● exception handling
● Run-Time parameters tuning and so on...
SObjectizer Team, Jan 2016
118. ...will be described in a more deep dive
But introductory part is finished.
SObjectizer Team, Jan 2016