Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-06-27 14:06:02


--- In boost_at_y..., Beman Dawes <bdawes_at_a...> wrote:
> Bill Kempf wrote:
>
> >4) Will the usage of this design make users opt for the native C
> >APIs instead of the Boost.Threads library.
>
> If you are worried (and I think it is a reasonable worry until we
have
> experience to tell us otherwise), then also provide the thread_id
(or
> thread_desc, thread_handle, or whatever you prefer to call it)
procedure
> oriented interface.

Duplicate interfaces for the same concept?
 
> I know that is asking you to do more work, but neither of these
seem
> huge. I'll volunteer to do the docs.

The extra work doesn't bother, it's the duplication of effort that
does. It seems like your asking us to develop two parallel designs
and let the users vote for the best, just to deprecate one later.
 
> If later we decide to deprecate one or the other, it would be on
the basis
> of hard experience and not just speculation.

You'll not want both, so one or the other will be deprecated. This
will break any code that (foolishly) chose the "loser".

> >5) Are the join(), detach() and destructor semantics
understandable
> >and usable?
>
> They seem OK to me, but what do developers with lots of threading
> experience say?
>
> > For example, what happens if you call a method on a
> >thread object that's been detached? I would expect an error.
>
> I would expect an error usually too. What are the semantics of
> is_alive()? Would is_current() also be an error?

I would expect them to be errors (exceptions thrown?). The thread
object is no longer referencing a thread (even if the original thread
is still running) making calls to is_alive() and is_current()
nonsensical.
 
> > If
> >that's the case should join() detach the thread, or simply wait
for
> >the thread to finish, being a noop if the thread is already
> >finished? wait for finish. detach() right away would be very
> counterintuitive to me, (but I only have very light thread
experience.)
>
> > Should the destructor detach() the thread or join() it?
>
> Prior examples seemed to show detach() as preferable. You
explicitly
> join() if you want to wait, otherwise just destroy the object.
Although I
> don't know if it is a big issue; if ~thread() has join() semantics,
you
> could always explicitly detach() first. Which would be a more
damaging
> mistake; to forget to do a join() and have the dtor detach(), or
forget to
> do a detach() and have the dtor join()?

Very good question, and it completely depends on what you expected to
happen. Forget to join and you get race conditions. Forget to
detach and you might deadlock. Both are serious possibilities.

> Thanks for keeping going through such protracted discussions!

Hey, it's important discussion, and I'm glad the thread concept is
finally getting it ;).

Let me summarize the two main interfaces that people seem to think
are the best, and try and flesh them out completely, adding more
questions as I go.

Interface 1:

class thread : boost::noncopyable
{
public:
        thread(detail::threadproc proc);
        ~thread();

        bool is_current() const;
        bool is_alive() const;

        void join() const;
        void detach();
};

void sleep(const xtime& xt);
void yield();

The constructor creates a thread from any function object (includes
function pointer) that takes no arguments.

The destructor shall either join() or detach(). See the comments
below.

The is_current() method returns true if the calling thread is the
same as the thread object. It's an error to call this method on a
detached thread object.

The is_alive() method returns true if the thread is still running.
It's an error to call this method on a detached thread object. In
general this method should not be used with a preference for join().
However, some busy wait situations can make use of this effectively.

The join() method waits for the thread to finish but the thread
object remains "attached". It's an error to call this method on a
detached thread.

The detach() method puts the thread object in a detached state in
which there is no longer a relationship between the object and the
running thread. This will allow the thread to simply "evaporate"
either when it finishes or the main thread is terminated.

Comments:

1. I still worry about the lack of self().

2. We need to figure out which is better, to detach() or join() in
the destructor. Conceptually detach() seems like the best
alternative, but this may complicate some user code where join()
needs to be called. Otherwise it seems to be a coin flip and we just
need to pick one and stick with it.

Interface 2:

namespace thread
{
   class ref
   {
   public:
      ref();
      ref(const ref& other);
      ~ref();

      ref& swap(const ref& other);
      ref& operator=(const ref& other);

      bool operator==(const ref& other) const;
      bool operator!=(const ref& other) const;

      bool is_current() const;
      bool is_alive() const;

      void join();
   };

   ref create(detail::threadproc proc);
   void sleep(const xtime& xt);
   void yield();
}

The actual thread state is an internal detail managed through a ref-
count which is "owned" by both ref objects and the executing thread.
So this state is deleted when BOTH the thread has finished AND there
are no more ref instances referring to the state. Because of this
there's no need for a detach() method.

The ref default constructor constructs a ref that doesn't refer to
any thread.

The ref copy constructor increments the ref-count on the thread state
data.

The ref destructor decrements the ref-count on the thread state.

The ref swap() method allows for fast swapping of ref objects and is
used for exception safe assignment.

The ref assignment operator uses swap() and the copy constructor to
insure exception safe assignment and proper ref-count manipulation.

The ref comparison operators can be used to compare ref objects to
see if they refer to the same thread.

The ref is_current() may eliminate the need for a self() method and
is used to see if the calling thread is the same as the thread
referred to by the ref object. See comments below.

The ref is_alive() method returns true if the referred to thread is
still running. In general this method should not be used with a
preference for join(). However, some busy wait situations can make
use of this effectively.

The ref join() method waits for the referred to thread to complete.

The create() method starts a new thread of execution and creates the
thread state needed, returning a ref object that refers to this.

Comments:

1. As with the first design I still think that self() may be
needed. However, self() is an easy method to add to this design,
while it's impossible to add to the previous one.

2. The ref-counting will make some operations slower (especially
since the ref-counting must be thread safe).

Other thoughts or comments on the two?

Bill Kempf


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