📜 ⬆️ ⬇️

Model of Actors and C ++: What, Why and How?

This article is a modified text version of the report of the same name from the C ++ CoreHard Autumn 2016 conference , which was held in Minsk last October. The desire to make this article arose under the impression that in the world of C ++, developers, as it were, are divided into two large and non-intersecting camps. In the first camp, there are experienced specialists who have seen everything, everyone knows and can do everything, behind which are dozens of hand-written implementations of the Actor Model, inside of which are tricky, of course, self-made, lock-free queues and state-of-the-art message handling mechanisms. Such professionals themselves can spend hours talking about the subtleties of multi-threaded programming (only for some reason they rarely do this). In the second camp, green newbies, who by fate have brought into the world of C ++, who still have little idea of ​​the differences between unique_ptr and shared_ptr, have only heard about patterns, and in the thread area they only have a superficial impression of std :: thread, std :: mutex and maybe std :: condition_variable. For people from the first camp, I can hardly tell you anything interesting, but I’ll try to briefly tell the developers from the second camp that the Actor Model in C ++ is normal. And there are a number of ready-made tools, by the example of which you can see what it is.


Introduction


The talk will be about the Model of Actors and whether it should be used in C ++ programs and, if necessary, then what can be used to not reinvent your own bike. We will speak about the Model of Actors as applied to the solution of problems of multithreading; therefore, the context should be narrowed so that there are no discrepancies.


Multithreading, as a tool, is used in two very different directions. The first is parallel computing . Multithreading is needed here for parallel execution of the same operations on different data blocks. Thereby greatly reducing the time to solve a specific computational problem. For example, transcoding a video file into one stream may take an hour. And transcoding into four parallel streams is only 15 minutes.


The second direction is concurrent computing . Those. simultaneous execution of many different operations. For example, a multi-threaded DBMS server that simultaneously receives requests, makes plans for their execution, performs I / O operations, gives results to clients, updates statistics, etc. Multithreading is needed here to ensure truly parallel execution of various operations. Although, by and large, concurrency can be ensured even on one stream (so-called quasi-parallelism).


And so further it will be a question of a multithreading with reference to concurrent computing. For it is in this direction that the use of the Actor Model is fully justified.


What is so complicated about multithreaded programming?


One of the biggest difficulties in multithreading is the mutable shared state.


As soon as we have an object that we need to modify from different streams, problems immediately begin. The more threads, the smarter the scenarios of their work, the harder it is to imagine the intricacies of the interaction between them. Hence, errors that are far from always easy to detect and fix.


How to simplify your life?


Do not divide anything. The principle of shared nothing , which is widely known in narrow circles.


Instead of having N threads that compete with each other for resources, you can make M threads, each of which will own its own data. None of the threads have access to the data of other threads.


Great, but what if thread X needed some information that thread Y has? Or if stream Y wants stream Z to update some data on itself?


Hence, the threads must somehow interact with each other. How?


At first glance, it seems that there are two options:


Or synchronously.
Either asynchronously.


However, synchronous interaction is not an option. Synchronous interaction of independent threads is also the same work with shared data. Only as shared data are the streams themselves.


Asynchronous interaction remains.


We approach the model of actors from afar ...


How can we communicate workflows on messages? For example, like this:



It turns out a picture in which there are threads, each thread has its own queue of incoming messages. Each thread takes messages from its queue and processes them. If the queue is empty, the thread sleeps until new messages arrive.


If thread X needs something from another thread Y, then thread X puts the message on the incoming queue of thread Y.


A nice addition: if the messages transfer a copy of the data, and not a link to the original data somewhere in the shared memory, then a sudden bonus is obtained - a transparent transition to distribution.


Indeed, if the flow Y gives all the necessary data to the message queue for Z, then it is no longer important whether the queue clears the flow Z in the same process, or it is the queue to send data to another network node. The message is self-contained, so its contents can be serialized, transmitted over the network, deserialized and delivered to the receiver.


Here we are on the fingers and showed some of the basic principles of the Model Actors.


Actually, Model Actors. In two or three words


The Model Actors appeared in 1973 thanks to the work of Carl Hewitt, and then it was developed in 1981 by William Clinger and in 1985 by Gul Agha.


Model Actors several times attracted widespread attention. The last wave of fame, according to a subjective impression, began to rise about 10-12 years ago. At first this was facilitated by the Erlang programming language. Then the Akka framework.


Those who want to immerse themselves in the theoretical part of the Model Actors can start with the following review articles on Wikipedia and further links:


History of the Actor Model
Actor Model
Actor model theory


However, immersion in the theory of the Actor Model is a pure leap into Computer Science. But I am not a scientist, but a software engineer in the past, a manager in the present, so I allow myself to concentrate only on practical aspects.


Model Actors "on the fingers"


If you do not go into the boring formal theory, the Actor Model is based on the following principles:



The principles are simple. And, when you get used to them, the obvious. However, one important point needs to be addressed separately. This moment is very important, because he explains why implementations of the Actor Model may look different and very different from each other.


More actors, good and different!


Actor is some kind of entity.


The actor model does not say exactly how this entity should be implemented.


Actor can be a separate process. For example, this happens in Erlang, where every lightweight process inside an Erlang VM can be considered an actor.


The actor can be a separate thread (OS thread, "green" thread, fiber, etc ...). For example, goroutines in the Go language can also be considered as actors (with a stretch).


An actor can be a separate object that wanders from one working context to another. For example, such are the actors in Akka and not only. There may be implementations in which, in general, all actors work on the context of a single thread.


What is the Actor Model associated with now?


Erlang


First of all - this is Erlang .


The irony is that I have never met Joe Armstrong saying that Model Actors influenced Erlang.

Erlang by itself. As well as attempts to create languages ​​more convenient for developers based on Erlang VM. For example, Elixir .


The history of Erlang began back in 1986 in one of the laboratories of the company Ericsson. Joe Armstrong experimented with Prolog for writing telephony software. As a result of these experiments, Erlang appeared.


In 1995, the unsuccessful development of the new telephone switch AX-N (by the way, in C ++) was closed in Ericsson. In the new development, Erlang was used as the main language. The result was a successful software and hardware product AXD301, within which there were about a million (!) Lines of code in Erlang .


True, the story of Erlang in Ericsson developed in a paradoxical way. Soon after the creation of the AXD301, the use of Erlang in the development of new products within Ericsson was prohibited. Joe Armstrong retired from Ericsson, founded his company, the language of Erlang entered OpenSource.


A few years later, when Erlang proved its worth while being in “free” navigation, the ban on Ericsson was lifted. In 2004, Armstrong returns to Ericsson.


Over the past 15 years, Erlang has proven its worth more than convincingly.
A huge number of products developed by Erlang, a number of companies use Erlang as a key tool.


For example, whatsapp .


Many companies are convinced of the advantages of Erlang and begin to use it at home. For example, Wargaming has formed a serious Erlang-development team (perhaps the most serious in the CIS) and is slowly re-educating Python sources for Erlang.


Let's look at a small example of the simplest program on Erlang. A classic example for the Model Actor is “ping-pong”:


-module(tut15). -export([start/0, ping/2, pong/0]). ping(0, Pong_PID) -> Pong_PID ! finished, io:format("ping finished~n", []); ping(N, Pong_PID) -> Pong_PID ! {ping, self()}, receive pong -> io:format("Ping received pong~n", []) end, ping(N - 1, Pong_PID). pong() -> receive finished -> io:format("Pong finished~n", []); {ping, Ping_PID} -> io:format("Pong received ping~n", []), Ping_PID ! pong, pong() end. start() -> Pong_PID = spawn(tut15, pong, []), spawn(tut15, ping, [3, Pong_PID]). 

Akka


The second most famous “icon” of the Model Actors is the Akka framework for Scala and Java languages.


The history of Akka can be started from the year 2006, when Philipp Haller (Philipp Haller) developed the implementation of the Actor Model for the Scala language. This implementation is included in the standard Scala library.


A few years later, when Scala and the actors from its standard library proved their viability, Jonas Boner (Jonas Bonér) in 2008 began to create the Akka framework , the first public version of which was released in the 2010th. One of the significant differences between Akka and Scala standard library actors was that Akka supported both Scala and Java. In this sense, it is noteworthy that the Lightbend company (formerly TypeSafe), which is behind Akka development and provides commercial support to Akka, has announced a shift in its focus from Scala in favor of Java.


Akka is widely used in the field of Web and online services (for example, Twitter, LinkedIn). The people behind Akka are involved in such modern buzz-words as Reactive Manifesto and Microservices .


Well, in order to make an impression about how the code looks using Akka, one of the ping-pong implementations for Akka on Scala (there are many such implementations, this one is found here ):


 import akka.actor._ case object PingMessage case object PongMessage case object StartMessage case object StopMessage class Ping(pong: ActorRef) extends Actor { var count = 0 def incrementAndPrint { count += 1; println("ping") } def receive = { case StartMessage => incrementAndPrint pong ! PingMessage case PongMessage => incrementAndPrint if (count > 99) { sender ! StopMessage println("ping stopped") context.stop(self) } else { sender ! PingMessage } } } class Pong extends Actor { def receive = { case PingMessage => println(" pong") sender ! PongMessage case StopMessage => println("pong stopped") context.stop(self) } } object PingPongTest extends App { val system = ActorSystem("PingPongSystem") val pong = system.actorOf(Props[Pong], name = "pong") val ping = system.actorOf(Props(new Ping(pong)), name = "ping") // start them going ping ! StartMessage } 

What is the strength in, brother?


Why did Erlang, Akka and other tools like them get so popular?


Here it would be appropriate to quote from Joe Armstrong (the creator of the Erlang language):


It is also possible that it will be possible to make it possible to parallel programming.

What a rough retelling, but with preservation of the meaning of the above, may sound like:


I also suspect that the advent of true multi-core CPUs will make programming of parallel systems using traditional mutexes and shared data structures difficult to impossible, and that just message exchange will become the dominant way of developing parallel systems.


I note several key factors that explain the success of Erlang and Akka:



But these are all secure languages ​​and managed environments.


And, by the way, the issues of fault tolerance and safe languages ​​that work in managed environments are quite strongly linked to each other. So, if some lightweight process inside the Erlang VM is divided by zero, then the Erlang VM will simply close this process alone and this will not affect the performance of other processes. However, if we take a multi-threaded application in C ++, which divides by zero in one of the threads, the entire application will crash.


So the fact that in recent years, the Model of Actors has become popular primarily in safe languages, there are some objective factors.


Is there any sense in implementations of the Actor Model for the C ++ language?


To answer this question, you must first answer another question: is C ++ needed at all now?


C ++ is an old language with a very long history. Even if we count from the moment of the official release (autumn of 1985), then he is already more than thirty years old. The name C ++ itself appeared in 1983, and work on the language began in 1979. Those. Soon we can talk about the forty-year history of the language.


During this time, C ++ has incorporated many innovations and borrowings. But it retained, at the same time, compatibility with a fair subset of C.


Those. became a real monster. It is unlikely that there are more than a couple hundred people in the world, about whom it can be said that they know C ++.


Using C ++ is difficult. C ++ is often criticized. Very often criticized deservedly.


The development of such languages ​​as Java / Scala / C # on the one hand, the growing popularity of functional languages ​​(such as OCaml and Haskell) on the other, as well as the emergence of new and "modern" alternatives, like Go, supplanted C ++ from many application niches in which by the will of fate was in the 1980s and 1990s.


Therefore, the last 15 years, the C ++ language has been regularly buried. I myself 8 years ago seriously believed that C ++ has no future.


However, C ++ is here. Alive and well. Successfully developed. It becomes more of a monster than it was. But, surprisingly, the more complex the C ++ language becomes, the easier it is to use in everyday work.


If we put aside religious attachments, it is easy to see that there is only one native language without GC in the mainstream, which makes it easy to switch from the lowest level close to the hardware to the very high level, like OOP and generalized programming. At the same time, this language is equipped with a wide range of tools, books and documentation, and various Internet resources. Plus a huge number of developers all over the world.


This language is C ++.


Therefore, if we need to do something complex and / or large, if at the same time we are not indifferent to either the speed of the resulting product or its resource intensity, if we don’t have an infinite budget and have deadlines, then C ++ will have one or two alternatives miscalculated.


With all my interest in Rust-y, I think that it will take him several more years of intensive development to become mainstream. But Swift cannot yet boast of cross-platform.

So whether we like it or not, but C ++ is still here. And, apparently, will be here for a long time.


And if so, and once in C ++, large and complex programs are developed, including those using multithreading, then why not simplify your life by using the Actor Model?


What is ready for C ++?


Let's see what for C ++ there is a ready-made implementation of the Actor Model. In order not to reinvent the wheel and be able to take something in existence, instead of giving something of your own from scratch. Well, or make sure that there is no implementation you need and it makes sense to kill a couple of person-years to create another solution.


Generally speaking, there are not so many ready-made implementations of the Actor Model. One of the lists can be found in Wikipedia: Actor Libraries and Frameworks . But there, unfortunately, not everything is listed and some of the projects no longer show signs of life.


Below we look at several implementations that are clearly alive, healthy, not just showing signs of life, but also evolving in an evolutionary way. In addition, these projects can boast portability across multiple platforms. For example, for this reason, the review does not include the Asynchonous Agents Library from Microsoft , which is available in Microsoft Visual Studio. Also, OOSMOS (sharpened primarily under pure C, and not under C ++) and actor-zeta (which is still at an early stage of its development) did not get into the review.


QP / C ++


Let's start with the QP / C ++ library .


QP / C ++ is a mature (more than 15 years of development) software under a dual license, designed for the development of embedded software, including real-time systems. Including systems that can work directly on bare metal. Including QP / C ++ partially complies with MISRA C ++ 2008. From all this, and results in its specificity. It is also the only framework in the review that is sufficiently C ++ 98.


Actors in QP / C ++ are called active objects and are hierarchical finite automata. Actor code can be typed as ordinary C ++ classes. And you can draw an actor in a special tool for visual modeling and its code will be generated automatically.


Active objects in QP / C ++ work on the context that QP allocates to them. Depending on the environment, the active objects can each work on their own thread, or they can share a common working context.


As an illustration, we will look at one of the examples from the QP composition, in which the LED on a device is periodically blinking.


The source file blinky.h, which declares the actor and everything connected with it:

 #ifndef blinky_h #define blinky_h using namespace QP; enum BlinkySignals { DUMMY_SIG = Q_USER_SIG, MAX_PUB_SIG, // the last published signal TIMEOUT_SIG, MAX_SIG // the last signal }; extern QMActive * const AO_Blinky; // opaque pointer #endif // blinky_h 

The main.cpp file in which the actor's operation is initiated:


 #include "qpcpp.h" #include "bsp.h" #include "blinky.h" int main() { static QEvt const *blinkyQSto[10]; // Event queue storage for Blinky BSP_init(); // initialize the Board Support Package QF::init(); // initialize the framework and the underlying RT kernel // instantiate and start the active objects... AO_Blinky->start(1U, // priority blinkyQSto, Q_DIM(blinkyQSto), // event queue (void *)0, 0U); // stack (unused) return QF::run(); // run the QF application } 

Well, the blinky.cpp file, in which the actor is implemented:


 #include "qpcpp.h" #include "bsp.h" #include "blinky.h" class Blinky : public QActive { private: QTimeEvt m_timeEvt; public: Blinky(); protected: static QState initial(Blinky * const me, QEvt const * const e); static QState off(Blinky * const me, QEvt const * const e); static QState on(Blinky * const me, QEvt const * const e); }; Blinky l_blinky; QMActive * const AO_Blinky = &l_blinky; // opaque pointer Blinky::Blinky() : QActive(Q_STATE_CAST(&Blinky::initial)), m_timeEvt(this, TIMEOUT_SIG, 0U) {} QState Blinky::initial(Blinky * const me, QEvt const * const e) { (void)e; // unused parameter // arm the time event to expire in half a second and every half second me->m_timeEvt.armX(BSP_TICKS_PER_SEC/2U, BSP_TICKS_PER_SEC/2U); return Q_TRAN(&Blinky::off); } QState Blinky::off(Blinky * const me, QEvt const * const e) { QState status; switch (e->sig) { case Q_ENTRY_SIG: { BSP_ledOff(); status = Q_HANDLED(); break; } case TIMEOUT_SIG: { status = Q_TRAN(&Blinky::on); break; } default: { status = Q_SUPER(&QHsm::top); break; } } return status; } QState Blinky::on(Blinky * const me, QEvt const * const e) { QState status; switch (e->sig) { case Q_ENTRY_SIG: { BSP_ledOn(); status = Q_HANDLED(); break; } case TIMEOUT_SIG: { status = Q_TRAN(&Blinky::off); break; } default: { status = Q_SUPER(&QHsm::top); break; } } return status; } 

Just :: Thread Pro: Actors Edition


The next tool is Just :: Thread Pro: Actors Edition .


Paid library from Anthony Williams, very famous in C ++ world. The author of the book "C ++ Concurrency in Action".


Actually, the advantages of the library end there :)


Each actor is allocated a separate OS thread. Accordingly, the number of actors that it makes sense to create inside the application is very limited.


As an illustration, look at the classic ping-pong example .


 #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(); } 

C ++ Actor Framework


The next tool is the C ++ Actor Framework . He is CAF, he is libcppa in the recent past.


OpenSource project under the BSD license.


If you can talk about the most famous implementation of the Model Actors for C ++, then this is about CAF. Perhaps there is no more expansive library on this topic for C ++.


CAF copies Erlang to C ++ as closely as possible. Therefore, if you know Erlang, you like Erlang, but you need to develop in C ++ and you would like to write in C ++ like Erlang, then you go straight to CAF.


The price for mimicry under Erlang is CAF high requirements for the level of standards support in C ++ in the compiler. Because of this, CAF developers have never considered Windows and VC ++ as one of the significant platforms for their development, limited to Linux, FreeBSD MacOS, as well as the latest versions of the gcc and clang compilers. In addition, the authors of CAF have stated several times that they will continue to focus primarily on the most new features of the C ++ language and will switch to features from the new standards as quickly as possible.


Note that CAF offers off-the-shelf tools for building distributed applications. For this, CAF has its own protocol for communicating remote agents and the implementation of this protocol through Boost :: Asio.


I have the most ambiguous impression of the CAF. Written in CAF, the code looks very cool and succinct in tiny examples. But it is not clear how convenient large and complex actors are implemented in CAFs.


In addition, during the two and a half years that CAF looms in my field of view, several times it has been declared that there is a violation of compatibility when new CAF versions are released.


Well, the developers of CAF also position it as a very smart framework, although some have reasonable doubts on this score;)


As an illustration, you can cite the example code fixed_stack from the CAF itself.


 #include <cassert> #include <cstdint> #include <iostream> #include "caf/all.hpp" using std::endl; using namespace caf; namespace { using pop_atom = atom_constant<atom("pop")>; using push_atom = atom_constant<atom("push")>; enum class fixed_stack_errc : uint8_t { push_to_full = 1, pop_from_empty }; error make_error(fixed_stack_errc x) { return error{static_cast<uint8_t>(x), atom("FixedStack")}; } class fixed_stack : public event_based_actor { public: fixed_stack(actor_config& cfg, size_t stack_size) : event_based_actor(cfg), size_(stack_size) { full_.assign( [=](push_atom, int) -> error { return fixed_stack_errc::push_to_full; }, [=](pop_atom) -> int { auto result = data_.back(); data_.pop_back(); become(filled_); return result; } ); filled_.assign( [=](push_atom, int what) { data_.push_back(what); if (data_.size() == size_) become(full_); }, [=](pop_atom) -> int { auto result = data_.back(); data_.pop_back(); if (data_.empty()) become(empty_); return result; } ); empty_.assign( [=](push_atom, int what) { data_.push_back(what); become(filled_); }, [=](pop_atom) -> error { return fixed_stack_errc::pop_from_empty; } ); } behavior make_behavior() override { assert(size_ < 2); return empty_; } private: size_t size_; std::vector<int> data_; behavior full_; behavior filled_; behavior empty_; }; void caf_main(actor_system& system) { scoped_actor self{system}; auto st = self->spawn<fixed_stack>(5u); // fill stack for (int i = 0; i < 10; ++i) self->send(st, push_atom::value, i); // drain stack aout(self) << "stack: { "; bool stack_empty = false; while (!stack_empty) { self->request(st, std::chrono::seconds(10), pop_atom::value).receive( [&](int x) { aout(self) << x << " "; }, [&](const error&) { stack_empty = true; } ); } aout(self) << "}" << endl; self->send_exit(st, exit_reason::user_shutdown); } } // namespace <anonymous> CAF_MAIN() 

SObjectizer


Well, the fourth framework, on which we dwell a little more - is SObjectizer .


OpenSource project under BSD license.


The project has been developing since 2002, although it is based on ideas that were developed and tested in the mid-90s when developing a small object-oriented SCADA system (the development of which, unfortunately, ended in the 2000th).


SObjectizer has never been an experimental project, it was created specifically to simplify the development of multithreaded software in C ++. Until now, software systems written on different versions of SObjectizer are in operation.


Therefore, in SObjectizer great attention is paid to compatibility. For example, in the fall of 2014, the SObjectizer version 5.5.0 was released. Since then, more than twenty releases have passed under version 5.5, the last stable version has the number 5.5.18, but there have been no breaking changes. So SObjectizer is a project with a very long history and a reverent attitude towards compatibility between versions.


The actors in SObjectizer are called agents. Just for historical reasons.


As in QP / C ++, the agents in a SObjectizer are, as a rule, instances of individual C ++ classes. As in QP / C ++, agents are hierarchical finite automata (including nested states, deep and shallow history, input / output handlers, time limits).


As in QP / C ++, the working context is provided by the framework to agents. For this, in SObjectizer there is such a thing as a dispatcher: a special entity that dispatches agent events. The composition of SObjectizer includes eight types of controllers available to the developer out of the box. Among them is such an interesting dispatcher, as adv_thread_pool, which allows you to simultaneously run event handlers of the same agent on different working threads if these handlers are marked as thread-safe.


What makes SObjectizer very different from the projects listed above is the symbiosis of the Actor, Publish-Subscribe and Comminicating Sequential Processes models.


In SObjectizer, messages are not sent directly to recipient agents, but to mboxes (mailboxes). And already from mbox, messages are delivered to those agents that are subscribed to it. Thus, mboxes in SObjectizer work as Topic-and in the model of Publish-Subscribe. Sending a message to mbox is like a Publish operation. Agents must perform a Subscribe operation to receive messages of interest to them.


This is what SObjectizer does not provide right now, so these are ready-made tools for building distributed applications. Such tools were in earlier versions of SObjectizer, but over time, for a number of objective reasons, they were abandoned and, starting from 2010, there are no similar tools in the core of SObjectizer. The user chooses which communication layer is more convenient for him to use - be it REST, MQTT, CoAP, AMQP or something else.


As an illustration, we will show the implementation of the CAF example of fixed_stack, but on SObjectizer (actually, personally this example seems strange to me, to say the least, not to say stupid, because there is no practical sense in creating such actors from the word, but since SObjectizer is often asked to compare it with the CAF, then let it be just such an example):


 #include <iostream> #include <so_5/all.hpp> class fixed_stack final : public so_5::agent_t { state_t st_empty{ this }, st_filled{ this }, st_full{ this }; const size_t m_max_size; std::vector< int > m_stack; public : class empty_stack final : public std::logic_error { public : using std::logic_error::logic_error; }; struct push { int m_val; }; struct pop : public so_5::signal_t {}; fixed_stack( context_t ctx, size_t max_size ) : so_5::agent_t( ctx ) , m_max_size( max_size ) { this >>= st_empty; so_subscribe_self() .in( st_empty ) .in( st_filled ) .event( &fixed_stack::on_push ); so_subscribe_self() .in( st_filled ) .in( st_full ) .event( &fixed_stack::on_pop_when_not_empty ); so_subscribe_self() .in( st_empty ) .event( &fixed_stack::on_pop_when_empty ); } private : void on_push( const push & w ) { m_stack.push_back( w.m_val ); this >>= ( m_stack.size() == m_max_size ? st_full : st_filled ); } int on_pop_when_not_empty( mhood_t< pop > ) { auto r = m_stack.back(); m_stack.pop_back(); this >>= ( m_stack.empty() ? st_empty : st_filled ); return r; } int on_pop_when_empty( mhood_t< pop > ) { throw empty_stack( "empty_stack" ); } }; int main() { try { so_5::launch( []( so_5::environment_t & env ) { so_5::mbox_t stack; env.introduce_coop( [&stack]( so_5::coop_t & coop ) { stack = coop.make_agent< fixed_stack >( 5u )->so_direct_mbox(); } ); for( int i = 0; i < 10; ++i ) so_5::send< fixed_stack::push >( stack, i ); std::cout << "stack { "; try { for(;;) std::cout << so_5::request_value< int, fixed_stack::pop >( stack, std::chrono::seconds(10) ) << " "; } catch( const fixed_stack::empty_stack & ) {} std::cout << "}" << std::endl; env.stop(); } ); return 0; } catch( const std::exception & x ) { std::cerr << "Oops! " << x.what() << std::endl; } return 2; } 

Conclusion


The Actor Model is a very convenient tool in cases where the use of this model is appropriate. This has been repeatedly proven by the successful use of tools such as Erlang and Akka in a wide variety of projects. . , .


: .


, C++. C++ . .


, . QP/C++ Just::Thread Pro . SObjectizer CAF — . .


, .


. . - - .


- :)


')

Source: https://habr.com/ru/post/322250/


All Articles