On Mon, Nov 8, 2010 at 4:53 PM, Marat Abrarov
To Dean Michael Berris:
Thank you for your patience and detailed answers.
No worries. :)
Nope, sorry, s11n_example::connection is not an active object. What it actually is, is an execution context -- or sometimes people call it a continuation, a generator, or a state machine. Because you're using the execution context relevant with a particular connection, it doesn't imply that the operations are performed on a different thread.
Ok. May be I'm wrong trying to name it "active object". "Continuation", "state machine", "execution context" - this is very close to what I had in mind. Therefore, I conclude that we understand each other (despite some language barriers with my hand). I thought all of "Continuation/state machine/execution context" was related to the pattern of "active object", hmm...
Nope, continuation, state machine, and execution context are independent of the active object pattern. There's a PDF somewhere of the web of what the active object pattern is supposed to provide and "look like". It might be good to look into the paper by Dr. Schmidt who introduced the concept a while back: http://www.cs.wustl.edu/~schmidt/PDF/Act-Obj.pdf
If you have unlimited number of cores and no limits on the number of threads you can spawn for each active object you create, then yes.
I think the main feature of "Continuation/state machine/execution context" is not saving threads, but organizing flexible and comfortable way of communication between state machines (yes - it is tasks and queues, but wrapped in async method call with callback).
Well, even there what you really need is a means of encapsulating tasks. If a task has dependencies and you follow the fork-join parallelism model where you spawn additional tasks and pass around a completion callback to do the "join", then that's really what you're looking for. You can look at how TBB does it with its dynamic partitioning to solve data-parallel problems using threads and tasks. This only works though if you have minimal need for synchronization across different tasks. There are many different ways of doing it, and the active object pattern really has nothing to do with massive parallelism.
That is what I try to implement by means of Boost.Asio. It was chosen "historically" (started from network programming), but for the now times I see it is the best choice :)
Hold on there. If you're doing data parallelism internal to an application, just having tasks might not be the best choice. In a massively parallel computation similar to what happens when you deal with graphical systems or with SIMD parallelism, using just Asio+Boost.Thread will not be sufficient (will even be suboptimal). This is why you really ought to look at Boost.MPI, Boost.Interprocess, Boost.Thread, and Boost.Asio for what their strengths are and choose which one you'll need. You choose the right tool for the job. What's really lacking in Boost is well-performing implementations of concurrent queue/hash/<insert data structure here> which we can use in implementing our own producer/consumer queue and other parallel partition-based algorithms. Asio just so happens to have a simple io_service which does the dispatch and queuing internally -- (ab)using that is not the best use of the library. Also, there is already a proposal for C++0x to include a std::thread_pool where you can throw tasks into. I'm not sure if Boost.Thread will implement this or whether there are already other libraries that do something similar, but that really is what you need.
Otherwise, you can stick with continuations, asynchronous completion handlers, and just plain data parallelism.
At the "samples" I develop (thanks to Boost.Asio examples - the are the first) the main goals were: 1) share execution context (thread pool) between different "state machines".
An execution context is not the thread pool. An execution context is usually an object which has state encapsulated which is relevant to the task. If you think about boost::bind(...) creating a function object that has bound parameters, then the function object is said to have its execution context encapsulated. This is how continuations work, and how stackless programming works as well -- you rely on the context that is available/local to the task/function when it is invoked at some point in time.
2) (make possible to) divide execution contexts of different "state machines" and application layers when it is needed (session and session_manager - not layers but are close to them).
You don't need Asio here.
3) develop a simple way of implementing async method call (for the "state machines") with required callback. (with required usage of "custom memory allocation" which can make usage of async methods as cheap as possible)
There is a pattern called the accumulator -- which is technically a function object that maintains its state across invocations. For example, you might have an object that keeps track of the number of times it's been called and every time its function operator overload is called, it returns the previous count. Like this: struct accumulator { accumulator() : count(0u) {} unsigned int operator()() { return count++; } unsigned int count; } In other programming languages that support closures, these would be something you might call a closure. The point here is that all you need to be able to perform asynchronous tasks and actions on different threads is for you to bring the data required for that operation to complete and package it up as a singular piece of computation as a function object. This is how tasks work, this is how Boost.Asio completion handlers are invoked, and this is how task-based programming works. If you discuss the fundamentals of these things instead of trying to go to complex examples to bring the point across, maybe it would be better, just a tip. ;) Also, using more canonical terms would help a lot as well. :D Oh, and please, don't top post. HTH -- Dean Michael Berris deanberris.com