The <jss/actor.hpp> provides
the jss::actor
and jss::actor_ref
classes for controlling and referencing actors.
namespace jss { class stop_actor; class no_actor; class actor; class actor_ref; }
Using Actors provides an easy way of structuring your application to use
multiple threads without worrying about the details of mutexes, condition
variables and other low-level facilities. Each jss::actor
runs its task (which can be a function or callable object) on its own thread,
and has an associated message queue.
Actors then send messages to each other either by calling jss::actor::send()
directly on an jss::actor
object, or on an jss::actor_ref
object. Messages can be of any copyable or movable type, so you can define
your messages in whatever way is most appropriate. The jss::actor::self()
function provides an easy way to
pass around a reference to the current actor. You can use this for passing
a reference to the current actor as part of a message, in order for the receiving
actor to send a response.
Messages are received by calling jss::actor::receive()
. Calling jss::actor::receive()
on its own will just receive and
discard all messages (as no matches are specified) until a jss::stop_actor
message is received (in which case a jss::stop_actor
exception is thrown). In order to process the messages you need to chain
a call to match
on the jss::actor::receive()
call, specifying the type of message
to match, and the function to call to handle it. This could be a lambda:
jss::actor::receive().match<my_message>([](my_message){ std::cout<<"my message received"<<std::endl; });
This will receive and discard all messages until either a my_message
message is received, in which case the lambda is run to print "my message
received" on standard output, or a jss::stop_actor
message is received. Once a message has been handled, jss::actor::receive()
returns to its caller. If you wish
to handle more than one message, just put the jss::actor::receive()
call in a loop.
You can also specify that you wish to process one of several types of message,
whichever arrives first. This can be done by chaining multiple match
calls:
jss::actor::receive() .match<first_message>([](first_message){ std::cout<<"first message type received"<<std::endl; }) .match<second_message>([](second_message){ std::cout<<"second message type received"<<std::endl; });
This time, jss::actor::receive()
will discard all messages until
it receives either a message of type first_message
or one of type second_message
(or a jss::stop_actor
message). Again, it returns as soon as one message has been processed, whichever
type it is. Any number of match
calls can be chained in this way, and the jss::actor::receive()
call will block until one of the
specified messages has been received.
Here is a complete example of actors playing ping-pong: each actor waits
in a loop for a pingpong
message. When it receives the message it either prints "ping" or
"pong", and sends a message back to the other actor. After 5 seconds
the main thread stops both the actors.
Note how the message type is just a simple struct
;
messages do not need to derive from a base class or implement a specific
interface, they just need to be move-constructible or copy-constructible.
Also, see how jss::actor::self()
is used to pass references between
actors, without either actor needing to know the identity of the other.
#include <jss/actor.hpp> #include <iostream> #include <thread> int main() { struct pingpong { jss::actor_ref sender; pingpong(jss::actor_ref sender_): sender(sender_) {} }; jss::actor pp1( []{ for(;;) { jss::actor::receive().match<pingpong>( [](pingpong p){ std::cout<<"ping\n"; p.sender.send(pingpong(jss::actor::self())); }); } }); jss::actor pp2( []{ for(;;) { jss::actor::receive().match<pingpong>( [](pingpong p){ std::cout<<"pong\n"; p.sender.send(pingpong(jss::actor::self())); }); } }); pp1.send(pingpong(pp2)); std::this_thread::sleep_for(std::chrono::seconds(2)); pp1.stop(); pp2.stop(); }