Boost logo

Boost :

From: Ove Svensson (ove.svensson_at_[hidden])
Date: 2003-02-05 13:01:35


Hi,

First I would like to apologize if what I am going to propose have already been
suggested, discussed and rejected. If that is the case, please ignore this
message.

I would like to suggest a new interface (and implementation) for boost:thread. A
problem with the current version (1.29.0), as I see it, is that it does not
distinguish between the two sides of a thread. On the one side we have the
creation of a new thread and interaction with that thread, e.g. joining. On the
other side we have the created thread and the operations one might want to
perform there, e.g. yielding.

True that the current implementation of boost:thread allows you create new
threads, interact with the thread and perform operations from within the
thread. The problem is that it is all mixed in a single interface.

If a created thread needs access to an instance of a boost::thread, then it must
be handled in either of two ways.

1. The creating side and the created thread must share a single instance of a
   boost::thread. For example:

   boost::thread* thr ;

   void thr_func()
   {
        thr->yield() ; // Yes I know that boost::thread::yield() is static.
   }

   int main()
   {
        thr = new boost::thread(thr_func) ;
        thr->join() ;
        delete thr ;
   }

   This solution has the obvious problem of having to share common data between
   two separate threads of execution.

2. The other solution is that the created thread gets its own unique instance of
   a boost::thread using the default ctor.

   void thr_func()
   {
        boost::thread self ;
        self.yield() ; // Once again, yes I know that yield() is static.
   }

   int main()
   {
        boost::thr thr(thr_func) ;
        thr.join() ;
   }

   This avoids the problem of having to share data between two threads of
   execution but introduces another. There is no association between the two
   instances of boost::thread. This makes it impossible to transfer information
   from the creating side to the created thread (yes, I know this is not part of
   the current boost::thread implementation but I think it should be).

Another problem with a single interface is the possibility of someone using the
wrong member operation in the wrong context, e.g. calling join() from within the
created thread. Yes, you would be stupid if you made such a mistake but why not
prevent it all together by a new interface to boost::thread.

Yet another problem with the current implementation is that the header file
exposes implementation specific details. This makes it very likely that
modification to the implementation or adding support for a new threading
environment will require a re-compilation of all code using boost::thread. Such
implementation specific details should, if at all possible, be hidden behind a
pimple.

What I would like to see is a new boost::thread implementation which meets the
following requirements.

a. There shall be two interfaces to a thread. One for creation of a thread, from
   here on called boost::thread. And, one for the created thread, from here on
   called boost::thread::self.

b. There shall exist a single, unique association between an instance of a
   boost::thread and an instance of a boost::thread::self. This association is
   from here on called boost::thread::context.

c. It shall be possible to create instances of boost::thread::self without an
   associated instance of boost::thread. This is mainly useful if one wants a
   boost::thread::self instance for the very first thread of execution, i.e. the
   thread which main() runs in.

d. If one does not want to perform any thread specific operations from within a
   thread, then it shall not be necessary to create an instance of a
   boost::thread::self.

e. There shall be no parent-child relationship between an instance of a
   boost::thread and an instance of a boost::thread::self, i.e. a thread which
   starts yet another thread shall not be required to wait for that other thread
   and termination of the first thread shall not lead to termination of the
   other thread. This means that an instance of a boost::thread can go out of
   scope without affecting the associated instance of a boost::thread::self.

f. It shall be possible to send extra information, as an optional extra argument
   to the boost::thread ctor, to the created thread. boost::thread::self shall
   offer a method for retrieving this extra information. It is not required that
   this information be passed in a type-safe manner, i.e. void* is okay.

g. It shall be possible for a thread to exit with a return value. It shall be
   possible for the creating side to retrieve, as a return value from join(),
   that value. It is not required that this value be passed in a type-safe
   manner, i.e. void* is okay.

h. Explicit termination of a thread, i.e. by calling boost::thread::self::exit()
   shall not lead to any resource-leaks.

i. Creation of a new thread of execution shall not require calls to any other
   method than the boost::thread ctor.

j. The header file shall not expose any implementation specific details.

Some additional features I would like to see.

k. It should be possible to control the behavior of a new thread, e.g. schedule
   policy, scheduling priority and contention scope.

l. It should be possible to cancel a thread and for a thread to control how is
   shall respond to cancellations.

m. Failure in any operation should be reported by throwing exceptions, not by
   assertions.

With all these requirements taken into account, I have come up with a new
implementation. My implementation is limited to pthread since that is the only
threading environment I have access to and knowledge about. Though I don't
think it should be difficult to add support for other threading environments
(since most of it can be re-used from the current boost::thread implementation).

If there exist any interest in the full implementation, please let me know and I
will make it publicly available.

Here is a striped down version:

// thread.hpp

namespace boost {

namespace detail {
struct thread_context ;
struct thread_attributes ;
} // namespace detail

class thread : private thread {
public:
        typedef detail::thread_context context ;
        typedef function<void* (const context&)> function ;
        class attributes ;
        class self ;
        explicit thread(const function&, void* xtra_arg= 0) ;
        thread(const attributes&, const function&, void* = 0) ;
        ~thread() ;
        void* join() ; // possibly const
        void cancel() ;
private:
        const scoped_ptr<context> context__ ;
public:
        class self : private noncopyable {
        public:
                self() ;
                explicit self(const context&) ;
                ~self() ;
                bool operator==(const self&) const ;
                bool operator!=(const self&) const ;
                void* arg() const ;
                void detach() ; // possibly const
                void exit(void* return_value = 0) ; // possibly const
                void yield() const ;
                void sleep(const xtime&) const ;
                enum cancel_state { ENABLE, DISABLE } ;
                enum cancel_type { ASYNCHRONOUS, DEFERRED } ;
                void cancelstate(cancel_state) ; // possibly const
                void canceltype(cancel_type) ; // possibly const
        private:
                const scoped_ptr<context> context__ ;
        } ; // class self
public:
        // class attributes left out
} ; // class thread

} // namespace boost

Here is an short example of how it might be used.

void* hello(const boost::thread::context& context)
{
        const boost::thread::self self(context) ;
        const char* who = static_cast<const char*>(self.arg()) ;
        std::cout << "Hello " << who << "!" << std::endl ;
        return 0 ;
}

int main()
{
        boost::thread::function func(hello) ;
        boost::thread thr(func, static_cast<void*>("world")) ;
        thr.join() ;
}

Here are some of the most interesting implementation details.

Here is the thread_context. The reason for the shared_ptr<pthread_t> is that
pthread_t is not always copyable.

// thread.cpp
namespace boost {
namespace detail {

struct thread_context {
        thread_context(const boost::thread::function* function = 0, void* arg = 0)
            : function__(function), arg__(arg), thread__(new pthread_t)
        {}
        const boost::thread::function* function__ ;
        void* const arg__ ;
        const shared_ptr<pthread_t> thread__ ;
} ; // struct thread_context

} // namespace detail
} // namespace boost

A new thread starts in thread_proxy(). The argument to thread_proxy() is a
pointer to a thread_context (casted to void*). The catch(thread_exit&) stuff is
being used to catch an exception thrown from boost::thread::self::exit(). The
reason for that exception is to ensure proper stack-unwinding and destruction of
local variables. Please note that thread_exit is defined in an anonymous
namespace within thread.cpp. Also note that all other exception types must be
caught in thread_proxy.

extern "C" {
static void* thread_proxy(void* param)
{
        void* rval = 0 ;
        try {
            boost::scoped_ptr<boost::thread::context> context(
                static_cast<boost::thread::context*>(param)
            ) ;
            rval = (*context->function__)(*context) ;
        }
        catch (thread_exit& x) {
              pthread_exit(x.rval__) ;
        }
        catch (...)
        {}
        return rval ;
}
} // extern "C"

To help in creation of a new thread I have the function create_thread(). Please
note that the thread_context which thread_proxy receives as an argument is being
created here. Unless creation of the thread fails, ownership of the
thread_context is being passed to thread_proxy (hence the scoped_ptr in
thread_proxy).

namespace boost {
namespace detail {

void create_thread(const thread_context& context,
                   const thread_attributes* attributes = 0)
{
        boost::thread::context* self_context =
                   new bost::thread::context(context) ;
       const int rc = pthread_create(
          &*context.thread__,
          attributes ? &attributes.attributes__ : 0,
          thread_proxy,
          self_context
       ) ;
       if (0 != rc) {
          delete self_context ;
          throw thread_resource_error() ;
       }
} // create_thread

} // namespace detail
} // namespace boost

Here are the boost::thread and boost::thread::self ctors.

namespace boost {

thread::thread(const function& thrfunc, void* arg)
    : context__(new context(&thrfunc,arg))
{
        detail::create_thread(*context__) ;
}

thread::self::self(const thread::context& context)
    : context__(new thread::context(context))
{}

} // namespace boost

As this posting has already become too long, I will leave the rest out until I
get some feedback.

/Ove Svensson


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk