
scott <scottw <at> qbik.com> writes:
In my opinion, the ActiveObject pattern can be divided into two operational aspects. One is where a "main thread" has one or more ActiveObjects available to it and the other is where ActiveObjects interact with each other.
If these operational architectures were referred to as asymmetric and symmetric activation, respectively, then my goals with respect to implementation of the pattern are focused on "symmetric activation".
Distinguishing these two architectures I feel, is crucial. Your latest implementation code includes "futures" _and_ "method requests". I acknowledge this (again . But there is no mechansim for the results of a "method request" to be returned _asynchronously_. For this reason I (and I am open to explanations of why I am wrong) view your implementation to be in the direction of asymmetric activation (AA).
Well, ok. Futures (in my code, and in the pattern) do allow a caller to access a result asynchronously, if you define asynchronously to mean that the caller is free to pursue other interests before acquiring the result. What is lacking is the ability to multiplex over events relating to the delivery of future values. You can't use select, poll or WFMO to determine whether any of an outstanding set of futures have been delivered. I think that this probably makes active objects an inappropriate choice for designs that are inherently reactive.
To highlight the significance of this target (i.e. AA) I will return to the database server example that several contributors have made reference to.
Lets say that the database server is most effective as a threaded, ActiveObject. Lets also say that due to the architecture of the underlying interface to an RDBMS, there are significant throughput gains to be made with 4 slave threads. If a GUI app is the "main thread", the database server is a threaded, ActiveObject and the slaves are threaded, ActiveObjects (all examples of AA) then the result is non-optimal. It would not realise the advertised benefits of 4 slave threads as the database server must block waiting for completion of a slave method (e.g. evaluation of a future).
You could apply the active object pattern to this design, but the simplest way to do so would be to constrain the slave threads to have void-returning methods, and to deliver the results of methods asynchronously, like this: // Psuedocode struct slave { void queueRequest(RequestData data, future<something>& futureResult) { // perform request server::deliverResult(data, futureResult); }; struct server { future<something> doSomething(void) { future<something> futureResult; slave::queueRequest(someRequest, resultResult); retuyrn futureResult; } void deliverResult(RequestData data, future<something>& futureResult) { futureResult.finalise(data); } }; struct client { something someMethod(void) { return server::doSomething(); }; }; (I think this is a variant on the 'half-sync, half-asynch' pattern?) Ideally, though, the server is a reactive design and not terribly suited to the active object pattern.
A cruder example of what I am trying to highlight is where a group of interacting ActiveObjects manage to create a "circular activation", i.e. A calls B, B calls C and C calls A. This is a fatal symptom of the underlying problem associated with AA.
I immediately concede that you may implement some sophisticated "event notification" between the database server and its slaves to solve this. You may even choose to not implement the slaves as ActiveObjects to give yourself the necessary "freedom".
Well, yes, you could (and it could certainly be generic, I'm sure). But it doesn't seem to be in the spirit of the design. Active object is a trade-off between simplicity at the client, and suboptimal performance at the server.
My response to this would be that the custom event notification is completely unnecessary. Application of an SA-focused ActiveObject removes the need for any such one-off mechanisms (and who wants to write those again, and again...). Finally, having deployed (SA) ActiveObjects I would be disappointed to see any new mechanism for thread communication. Proof of a successful implementation of SA (IMHO) would be that it became the _only_ mechansim for inter-thread communication. Of course, in the real world, this is not going to happen but I would offer it as a noble intent
This is certainly achievable, but is it using the active object design? You can implement an exactly equivalent design using the platform AIO primitives, but this would yield code which was much harder to follow and implement than active objects communicating through function calls. If you want optimum throughput throughout the interracting components, I don't think active object is the pattern you're looking for.
I tentatively suggest that the ActiveObject that most of us want is the AA ............................<Mentally changed to 'SA', as per your followup> variety. We can see the objects exchanging the Method Requests in a symphony of optimal operation - in our heads. But between our heads and the "tools at hand" I believe the symphony becomes something else, primarily due to AA-based environments and associated culture.
Having made my case (as best I can) things now get muddy. Firstly I dont see any mechansim for the delivery of asynchronous results in the pattern! Please note that I wanted to verify this claim but have failed to connect to siteseer for the last two hours. In my ActiveWorld the same mechansim that is used to queue Method Requests (in your implementation - "tasks") is also used to queue results.
The asynchronous delivery mechanism is the conversion of the 'future' template object to it's parameterised type. For example, the following blocks: future<int> f = someActiveObject.someMethod(); // Non-blocking int i = f; // Blocks until result delivered Of course, the caller may not need the result yet, and can postpone the wait indefinitely: std::vector<future<int> > results; results.push_back(someActiveObject.someMethod()); // Non-blocking ... // Whatever
While I can see the pragmatic value in including both AA and SA in all ActiveObject initiatives, I also wonder about the psychology of developers. While implementation of SA ActiveObjects is a little bit more difficult I suspect there are other reasons that it doesnt "catch on". Firstly, we tend to shy away from new, foreign models of execution and secondly AA is always there to "fall back" on.
It might be interesting to note that my ActiveWorld implementation is _completely_ SA. The very pleasant surprise for me has been the successful manner in which it has been deployed in existing codebases. It may appear foreign but our best coding is naturally trying to emulate the SA ActiveObject.
What interface are you using to allow active objects to access results generated by other active objects?
Cheers, Scott
ps: I mentioned the "anything declared in private scope is thread-safe" to see if we were "on the same wavelength". After reading your response(s) I think the answer to that is "yes". Or am I deluding myself Any _data_ declared with private scope in the "struct object" would only be accessible to the methods of that same struct and those methods are only every called by "boost::thread thread" in "class active". Voila! We dont need any mutexes around that data!
Yes, but that only applies to a single-threaded active object. Matt