The active object design pattern decouples method execution from method invocation for objects that each reside in their own thread of control.The goal is to introduce concurrency, by using asynchronous method invocation and a scheduler for handling requests. (Wikipedia:Active Object)
The Active Object decouples method invocation from method execution. The method invocation is performed on the client thread, but the method execution is on the Active Object. The Active Object has its thread and a list of method request objects (short request) to be executed. The client’s method invocation enqueues the requests on the Active Object’s list. The requests are dispatched to the servant.
Also known as
- Concurrent object
When many threads access a shared object synchronized, the following challenges must be solved:
- A thread invoking a processing-intensive member function should not block the other threads invoking the same object for too long.
- It should be easy to synchronize access to a shared object.
- The concurrency characteristics of the executed requests should be adaptable to the concrete hardware and software.
- The client’s method invocation goes to a proxy, which represents the interface of the Active Object.
- The servant implements these member functions and runs in the Active Object’s thread. At run time, the proxy transforms the invocation into a method invocation on the servant. This request is enqueued in an activation list by the scheduler.
- A scheduler’s event loop runs in the same thread as the servant, deques the requests from the activation list, removes them and dispatches them to the servant.
- The client obtains the method invocation result via a future from the proxy.
- The proxy provides an interface for the accessible member functions on the Active Object. The proxy triggers the construction of a request into the activation list. The proxy runs in the client thread.
- The method request class defines the interface for the method executing on an Active Object. This interface also contains guard methods, indicating if the job is ready to run. The request includes all context information to be executed later.
- The activation list maintains the pending requests. The activation list decouples the client’s thread from the Active Object thread. The proxy inserts the request object, and the scheduler removes them. Consequently, the access to the activation list must be serialized.
- The scheduler runs in the thread of the Active Object and decides which request from the activation list is executed next. The scheduler evaluates the guards of the request to determine if the request can run.
- The servant implements the Active Object and runs in the active object’s thread. The servant implements the interface of the method request, and the scheduler invokes its member functions.
- The proxy creates the future and s only necessary if the request returns a result. Therefore, the client receives the future and can obtain the method invocation result on the Active Object. The client can wait for the outcome or poll for it.
- Request construction and scheduling: The client invokes a method on the proxy. The proxy creates a request and passes it to the scheduler. The scheduler enqueues the request on the activation list. Additionally, the proxy returns a future to the client if the request returns a result.
- Member function execution: The scheduler determines which request becomes runnable by evaluating the guard method of the request. It removes the request from the activation list and dispatches it to the servant.
- Completion: When the request returns something, it is stored in the future. The client can ask for the result. When the client has the result, the request and the future can be deleted.
Pros and Cons
- Synchronization is only required on the Active Object’s thread but not on the client’s threads.
- Clear separation between the client (user) and the server (implementer). The synchronization challenges are on the implementer’s side.
- Enhanced throughput of the system because of the asynchronous execution of the requests. Invoking processing-intensive member functions do not block the entire system.
- The scheduler can implement various strategies to execute the pending requests. If so, the jobs can be executed in a different order they are enqueued.
- If the requests are too fine-grained, the Active Object pattern’s performance overhead, such as the proxy, the activation list, and the scheduler, may be excessive.
- Due to the scheduler’s scheduling strategy and the operating system’s scheduling, debugging the Active Object is often tricky. In particular, if the jobs are executed in a different order, they are enqueued.
runmember function of the Active Object creates the threads.
future<vector<future<pair<bool, int>>>>are often quite verbose. To improve the readability, I heavily applied using declarations (line 1). This example requires a solid knowledge of promises and futures in C++. My posts about tasks provide more details.
activeObjectand a number
numberPrimesrandom numbers are generated (line 3) between 1’000’000 and 1’000’000’000 (line 4) and pushed on the return value:
future<pair<bool, int>holds a
int. The bool indicates if the
intis a prime. Let’s have a closer look at line (5):
futures.push_back(activeObject.enqueueTask(numb)). This call triggers that a new job is enqueued on the activation list (line 6). All calls on the activation list have to be protected. The activation list is a deque of promises (line 7):
deque<packaged_task<pair<bool, int>()>>. Each promise performs the function object
IsPrime(line 8) when called. The return value is a pair of a
int. The bool indicates if the number
activeObject.run()in line (11) starts the execution. The member function
run(line 12) creates the thread to execute the member function
runNextTask(line 14) determines if the deque is not empty (line 15) and creates the new task. By calling
futResults.push_back(fut.get())(line 16) on each future, all results are requested and pushed on
futResults. Line (17) sorts the vector of pairs:
vector<pair<bool, int>>. The remaining lines present the calculation. The iterator
prItin line 18 holds the first iterator to a pair that has a prime number.
distance(prIt, futResults.end())(line 19) and the primes (line 20). Only the first non-primes are displayed.
The Active Object and Monitor Object synchronize and schedule member function invocation. The main difference is that the Active Object executes its member function in a different thread, but the Monitor Object is in the same thread as the client. In my next post, I will present the Monitor Object.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, and Marco Parri Empoli.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,