[cpp-threads] Re: Thread API interface tweaks

Ion Gaztañaga igaztanaga at gmail.com
Wed Aug 30 13:18:57 BST 2006


> Peter's design adds an extra start-function wrapper around even what
> thread sees, necessitating a different launcher.  That appears key to
> having futures be non-intrusive.

This sounds familiar to me ;-) Essentially, Peter has designed the
same implementation of my Kevlin's N1883 approach. I, like Peter,
implemented future (joiner, in my/Kevlin's library) as a shared_ptr of
an implementation class. In that paper I proposed standardizing the
implementation, so that everyone could create it's own factories
("threaders" in my library). I implemented Peter's thread_executor and
pool_executor in the same way:

//Launches task t in a thread
joiner<int> j = thread(t);

//Launches task t with a thread pool
joiner<int> j2 = thread_pool(t);

Now rename joiner->future and thread->executor and you have the same.
In my opinion, this was why separating threader and joiner was
important. I tried to explain that in the meeting but I didn't make a
good job. Separating threader/factory from joiner/future is important
if you want to have different factories that return the same future
type. This allows mixing futures of different sources in a container,
for example. the user does not know how the task is going to be
executed.

With a future that delegates the implementation to another class via
virtual functions we can design a library that returns a future using
a thread for each operation (imagine a network dns operation). In the
next version we can use thread-pools or operating system's own
asynchronous network operations but we still return the same future.

This approach needs dynamic allocation, but the fact is that in most
implementations, the return value/object function is going to be
allocated in the heap, or we need to use mutex/conditions to notify
events between the future and the executor (which in my opinion, has a
bigger cost than dynamic allocation).

This approach was implemented and described here:

http://ice.prohosting.com/newfunk/boost/libs/thread_new/doc/html/index.html

The request for extensions above N1883 is here:

http://ice.prohosting.com/newfunk/boost/libs/thread_new/doc/html/thread_new/beyond_N1883.html

The code (built above Boost.Threads) can be downloaded from Boost.Vault:

http://www.boost-consulting.com/vault/index.php?direction=0&order=&directory=Concurrent%20Programming

Herb also suggested that "futures" should not be tied with an
operating system thread. In that discussion, we were talking about
Howard's "thread" class. Well, I think that "thread" should be
directly tied with the operating system thread, whereas "future"
(joiner, in Kevlin's terminology) should be a higher level concept for
an asynchronous task . Peter has taken that approach, and my
implementation of joiners was also suggesting the standardization of
the implementation, so that a user can create its own
factories/threaders and produce the same joiner types.

This runtime "generic" future, however, shouldn't replace Howard's
"thread" class. "thread", IMHO, should be used to represent an OS
thread, because:

-> We might want to launch an operating system thread (I'm thinking
mainly in a posix style, processes with shared memory, in Nick's own
words), because we want to take advantage of its properties (signals,
scheduling, etc...)

-> "thread" is capable of moving the return value, something that I
consider important.

-> "thread" can be very efficiently implementated if it has not to
track attached futures, like in Howard's approach.

Then we can create "executors" (one thread per task, or a thread pool,
for example) above that "thread" abstraction. "thread" would be, then,
a simple, efficient wrapper, capable of moving the return type.
future, on the other hand, should be a handle for a previously
launched asynchronous task.

Take in care that we can also design futures that we can join at the
same time but different threads or that don't have reference counting
semantics. This will also offer the possibility of different executors
returning the same future, but the future would be very efficient and
move-capable. So maybe there is room for finer-grained task
abstractions, between "thread" and "future"

But even if we are capable or launching multiple parallel operations,
the problem is how are we supposed to wait/join them efficiently
without OS support.

Regards,

Ion



More information about the cpp-threads mailing list