[omniORB] Hello and Native NT Thread problem.

Saroj Mahapatra saroj@Bear.COM
Mon, 20 Apr 1998 12:40:09 -0400


[snipped]

In my last posting, I only wrote about omni_condition,
because omni_thread is a separate issue.

omni_mutex, omni_condition, omni_semaphore  follow pthread_xxx_create
and pthread_xxx_destroy model.  So it is good to create wrapper
classes which create the OS object in the constructor and
destroy it in the destructor.  As long as these wrappers store the OS
objects and provide member functions to manipulate them without making
any other assumption(which it can not enforce by its function prototype)
(omni_condition implementation on NT for example), we are OK.

A thread on the other hand should not be destroyed in  the destructor
of its wrapper class. omni_thread should be a 'handle' to the
thread entity (object). The life-time of the 'handle' object and the
thread object should be independent.  To make this more clear, this
is a class I wrote sometime back for my class library:  (it is applicable
for other OS threads too):

// Thread class in the Thr namespace.

namespace Thr {  // if your compiler does not have namespace
                 // use #define namespace struct
  enum DetachState { detached, joinable };
  class Thread;
};

class Thr::Thread
{
public:
  typedef void* (*StartFunction)(void* arg);
  typedef pthread_t OsPrimitive;

  Thread(StartFunction start_func, void* arg, Thr::DetachState detach_state);

  // Note that, when it is destroyed, it does not exit/cancel the thread
  // entity which it creates in the constructor. The thread entity exits
  // when it returns from the start_func. So copying a Thread object is
  // legal (multiple handles to the same entity).

  void join();
  OSPrimitive self();
  ....
private:
  pthread_t thread_;
};


This way of wrapping a thread object has several advantages:

* simplest possible interface and implementation;
  ultra-thin wrapper of the OS function.

* no allocation on the heap. The OS primitve is internally allocated
  on the heap and I do not want to make yet another heap allocation.

* It is really not useful to follow the Java model of creating thread
  in one step and running it in another.  This class is just like
  pthreads.  Another chance of error is eliminated and you have exactly
  same functionality. It is more efficient too,
  as we do not have to lock and unlock to protect the internal state
  (started/running etc).

* This class should not be derived from (unlike Java thread), but should
  be stored as a member variable in another class (if needed). In this
  respect, it is like the standard C++ library containers.

* It is easy to use too:

    class OmniNamesLog
    {
    public:
      OmniNamesLog() {
 Thr::Thread timer_thread(check_point, this, Thr::detached);
 // note: it is a local variable; no need to store it as a member.
 // if it is joinable or we want to reference it for another
 // reason, then use a member variable.
      }
    private:
      list<Binding> bindings_;  // list to store
      static void* check_point(void* arg) {
 OmniNamesLog* log = (OmniNamesLog*)arg;
 // can use log->bindings_ or call a member function.
 return 0;
      }
    };

    The containing class (here OmniNamesLog) can store any additional
    data needed by the thread function.


* The only gotcha is void* cast in 'start_func'. So we change it to :

class Thr::Thread
{
public:
  class Action
  {
  public:
    virtual ~Action() {}
    virtual void* run() = 0;
  };

  Thread(Action* start_action, Thr::DetachState detach_state) {
    pthread_create(&thread_,
     detach_state == Thr::detached ? &detached_.attr_ : 0,
     start_it, start_action);
  }

  void join();

public:
    // should be private, but for SUNpro
    // compiler bug.
  struct Detached {
    pthread_attr_t attr_;
    Detached();
    ~Detached();
  };

private:
  pthread_t thread_;
  static Detached detached_;
  static void* start_it(void* arg) {
    ((Action*)arg)->run();
    return 0;
  }
};

// ThrAction : makes it easy to use member functions as thread action.

template<class T>
class ThrAction : public Thr::Thread::Action
{
public:
  typedef void* (T::*MemberFunction)();

  ThrAction(T* object,  MemberFunction mf)
    : object_(object), mf_(mf) {}

  void* run() {  // call the member function; is virtual
    return (object_->*mf_)();
  }
private:
  T* object_;
  MemberFunction mf_;
};


*   Now OmniNamesLog can be alternatively written as:

class OmniNamesLog
{
public:
  OmniNamesLog() {
    ThrAction<OmniNamesLog> check_point(this, do_check_point);
    // do_check_point member function (non-static) will
    // be called by the new thread.
    Thr::Thread timer_thr(&check_point, Thr::detached);
  }
private:
  list<Binding> bindings_;  // list to store
    void* do_check_point() {  // Note: non-static member function
      // directly use bindings_.
      // no casting from void*
      return 0;
    }
  };

* The only gotcha is virtual-ness of run(). So my favorite strategy is:

// When I have namespace support, it will be Thr::Thread.

template<class Action>
class ThrThread
{
public:
  ThrThread(Action* start_action, Thr::DetachState detach_state);

  // When compilers implement member templates, we can have a constructor
  // template rather than class template.

  void join();

public:    // should be private, but for SUNpro
    // compiler bug.
  struct Detached {
    pthread_attr_t attr_;
    Detached() {...}
    ~Detached() {.. }
  };

private:

  pthread_t thread_;
  static Detached detached_;
  // If your compiler has problems with template static members, then
  // you have to create and destroy a pthread_attr_t  in the constructor.

  static void* start_it(void* arg) {
    ((Action*)arg)->run();
    return 0;
  }
};

template<class T>
class ThrAction  : same as above, but has no base class and no virtuals now.

template<class Action>
  ThrThread<Action>::Detached ThrThread<Action>::detached_
  = ThrThread<Action>::Detached();

template<class Action>
ThrThread<Action>::ThrThread(Action* start_action,
        Thr::DetachState detach_state)
{
  int rc;
  if (rc = pthread_create(&thread_,
     detach_state == Thr::detached
     ? &detached_.attr_ : 0,
     start_it, start_action))
 ...;
}

* We can write OmniNamesLog as :

OmniNamesLog::OmniNamesLog()
{
  typedef ThrAction<OmniNamesLog> CheckPoint;
  CheckPoint cp(this, do_check_point);
  ThrThread<CheckPoint> timer_thr(&cp, Thr::Detached);
}

* Now we have a solution, which is both efficient and convenient.
  (BTW, I like C++ more than Java, because it allows me choose the
   right trade-offs in a design).

Hope it helps,
Saroj Mahapatra




***********************************************************************************
Bear Stearns is not responsible for any recommendation, solicitation, offer or
agreement or any information about any transaction, customer account or account
activity contained in this communication.
***********************************************************************************