How to Use the ROOT TThread Class
 

Jörn Adamczewski, Marc Hemberger
 

Mar 3, 2000
updated September 26, 2001 (J.A.)

    Contents

  1. Summary

  2. 1.1  Disclaimer
    1.2  Problems
  3. Installation
  4. Threads related ROOT Classes
  5. TThread for pedestrians

  6. 4.1  TThread user function
  7. TThread in more detail

  8. 5.1  CINT
    5.2  Asynchronous Actions
    5.3  Synchronous Actions: TCondition
    5.4  Xlib connections
    5.5  Canceling a TThread
    5.6  Finishing thread
  9. Advanced: TThreads with classes

  10. 6.1  How to launch a class memberfunction as TThread
  11. Thanks and Credits
  12. List of Example files

  13. 8.1  Example mhs3
    8.2  Example conditions
    8.3  Example TMhs3
Thread Usage

Glossary

For the PostScript-text, click here.

1  Summary

1.1  Disclaimer

The ROOT framework is not thread-safe in general. There are some known problems the core developer team of ROOT is aware of and is working on. For this reason, it should clearly be stated, that TThread is by no means a solution for everything. Be aware of possible problems, restrict your threads to distinct and `simple' duties, and you will benefit from their use.

The TThread-class can be used on all platforms which provide a POSIX compliant thread implementation. On Linux Xavier Leroy's LinuxThreads implementation is widely used, but the TThread implementation should be usable on all platforms which provide pthreads.

1.2  Problems

  1. Linux Xlib on SMP machines is not threadsafe yet, which may cause a crash during threaded graphics operations; this problem is independent of ROOT.
  2. Object instantiation: no implicit locking mechanism for memory allocation and global root lists, the user has to take care for this on using threads.

2  Installation

For the time being (i.e. up to ROOT 2.23/xx), it is necessary to compile a threaded version of ROOT to achieve some very special treatment of the canvas operations. This will not be necessary later on anymore.

To compile ROOT, just do:

./configure linuxdeb2 --with-thread=/usr/lib/libpthread.so


This sets up the environment to use /usr/lib/libpthread.so as the Pthread library, and to switch on a define R__THREAD. This enables to have the thread specific treatment of gPad, and to get the $ROOTSYS/lib/libThread.so.
Note: Parameter linuxdeb2 has to be replaced with the appropriate ROOT keyword for your linux distribution.

gmake depend
gmake


This compiles ROOT as usual.

3  Threads related ROOT Classes

TThread
This class implements threads. A thread is an execution environment much lighter than a process. A single process can have multiple threads. The actual work is done via the TThreadImp class (i. e. TPosixThread).
TMutex
This class implements mutex locks. A mutex is a mutual exclusive lock. The actual work is done via the TMutexImp class (i. e. TPosixMutex)
TCondition
This class implements a condition variable. Use a condition variable to signal threads. The actual work is done via the TConditionImp class (i. e. TPosixCondition).
TSemaphore
This class implements a counting semaphore. Use a semaphore to synchronize threads. The actual work is done via the TMutexImp and TConditionImp classes.

4  TThread for pedestrians

4.1  TThread user function

To run a thread within ROOT, you have to follow these steps:
Initialization
Add these lines to your rootlogon.C:
{
   printf("\nWelcome to the ROOT session\n\n");
   gSystem->Load("/usr/lib/libpthread.so");
   gSystem->Load("$ROOTSYS/lib/libThread.so");
}
This loads the library with the TThread class and the Pthread specific implementation file for Posix threads.
Define
a function (e.g. void* UserFun(void* UserArgs)) that should run as a thread. See the example mhs3.cxx and mhs3.h or CalcPiThread.cxx and CalcPiThread.h. From these functions we create with the Makefile.mhs3  or Makefile.CalcPiThread a shared library to be loaded into the interactive ROOT session. How to go on with member functions will be explained in section 6.
Start
your interactive ROOT session
Load
the shared library with gSystem®Load("mhs3.so"); or
    gSystem®Load("CalcPiThread.so");
 
Create
the thread instance (see also example RunMhs3.C or RunPi.C) by
TThread *th = new TThread(UserFun,UserArgs);
You can also give the thread a self defined name, then it might be called via
TThread *th = new TThread("MyThread", UserFun, UserArgs);
This method is appropriate to use threads in compiled code if you want to access them by name. Normally, the function name ``UserFun'' will be the name of the thread (this does not work if TThread constructor is called from compiled code).

Arguments to the thread function can be handed over via UserArgs-pointer. When you want to start a method of a class as a thread, you have to give the pointer to the class instance as UserArgs (see examples in section 6).

Run thread
by th®Run();
Control
TThread::Ps(); - like UNIX ps command;
With the mhs3-example (example 8.1), you should be able to see a canvas with two pads on it. Both pads keep histograms updated and filled by three different threads.

With the CalcPi-example (example 8.4), you should be able to see two threads calculatng Pi with the given number of intervals as precision.

5  TThread in more detail

5.1  CINT

CINT is not thread safe yet. CINT execution will block the execution of the threads untill CINT has finished.

5.2  Asynchronous Actions

Different threads can work simultaneously with the same object. Some actions can be dangerous. For example, when two threads create a histogram object, ROOT allocates memory and puts them to the same collection. If it happened to be at the same time, some clashes are possible. To avoid this, the user has to synchronize these actions:
  TThread::Lock()    // Locking the following part of code

  ...                // Create an object, etc...

  TThread::UnLock()  // Unlocking 

The part of code between Lock() and UnLock() will be performed separately. No other thread can perform actions or access objects/collections at the same time. The TThread::Lock() and TThread::UnLock() methods internally use one global TMutex instance for locking. The user may also define his own TMutex MyMutex instance and may protect his asynchronous actions by calling MyMutex.Lock() and MyMutex.UnLock().

5.3  Synchronous Actions: TCondition

To synchronize the actions of different threads the TCondition class can be applied which provides a signaling mechanism.

The TCondition instance must be accessible by all threads which want to use it, i.e. it should be a global object (or a member of the class which owns threaded methods, see section 6). To create a TCondition object, a TMutex instance for locking of Wait and TimedWait methods is required. One can pass the address of an external mutex to TCondition constructor:

  TMutex MyMutex;  
  TCondition MyCondition(&MyMutex);
If NULL is passed, TCondition creates and uses its own internal mutex:
  TCondition MyCondition(NULL);
From then on it is possible to use the following methods of synchronization:
  1. TCondition::Wait() waits until any thread sends a signal of the same condition instance: MyCondition.Wait() reacts on MyCondition.Signal() or MyCondition.Broadcast(); MyOtherCondition.Signal() has no effect.
  2. If several threads wait for the signal of the same TCondition MyCondition, at MyCondition.Signal() only one thread will react; to activate a further thread another MyCondition.Signal() is required, etc.
  3. If several threads wait for the signal of the same TCondition MyCondition, at MyCondition.Broadcast() all threads waiting for MyCondition are activated at once. Remark: In some tests of MyCondition using an internal mutex, Broadcast() activated only one thread (probably depending whether MyCondition had been signalled before).
  4. MyCondition.TimedWait(secs,nanosecs) waits for MyCondition until the absolute time in seconds and nanoseconds since beginning of the epoch (January, 1st,1970) is reached; to use relative timeouts ``delta'', it is required to calculate the absolute time at the beginning of waiting ``now''; a possible coding could look like this:
  5. Ulong_t now,then,delta;              // seconds 
    TDatime myTime;                      // root daytime class 
    myTime.Set();                        // myTime set to ``now'' 
    now=myTime.Convert();                // to seconds since 1970 
    then=now+delta;                      // absolute timeout 
    wait=MyCondition.TimedWait(then,0);  // waiting
    Return value wait of MyCondition.TimedWait should be 0, if
    MyCondition.Signal() was received, and should be nonzero, if timeout was reached.
The conditions example (example 8.2) shows how three threaded functions are synchronized using TCondition: ROOT macro condstart.C starts the threads which are defined in a shared library (conditions.cxx, conditions.h).

5.4  Xlib connections

Usually Xlib is not thread safe. This means that calls to the X could fail, when it receives X-messages from different threads. At first this depends very crucially on the Xlib-version you have installed on your system. The only thing we can do here within ROOT is calling a special function XInitThreads() (which is part of the Xlib), which should (!) prepare the Xlib for the usage with threads.

To avoid further problems within ROOT some redefinition of the gPad pointer was done (that's the main reason for the recompilation). When a thread creates a TCanvas, actually this object is created in the main thread, and this is hidden from the user. Actions on the canvas are controlled via a function which returns a pointer to either thread specific data (TSD) or the main pointer. This mechanism works currently only for gPad and will also be implemented for other global Objects as e. g. gVirtualX, gDirectory, gFile.

5.5  Canceling a TThread

Canceling of a thread is a rather dangerous action. In TThread canceling is forbidden by default. But the user can change this default by calling TThread::SetCancelOn(). There are two cancellation modes:
Deferred
- Set by TThread::SetCancelDeferred() (default): When the user knows safe places in his code where a thread can be canceled without interference with the rest of the system, he can define these points by invoking TThread::CancelPoint(). Then, if a thread is canceled, the cancelation is deferred up to the call of TThread::CancelPoint() and then the thread is canceled safely. There are some default cancel points for Pthreads implementation, e.g. any call of

TCondition::Wait(), TCondition::TimedWait(), TThread::Join().
Asynchronous
- Set by TThread::SetCancelAsynchronous(): If the user is sure that his application is cancel safe, he could call:
  TThread::SetCancelAsynchronous();
  TThread::SetCancelOn();
  // Now cancelation in any point is allowed.
  ...  
  ...
  //  return to default  
  TThread::SetCancelOff();
  TThread::SetCancelDeferred();
To cancel a thread TThread* th the user can call:
th®Kill();
thread th is canceled
TThread::Kill(name);
cancel by thread name;
TThread::Kill(tid);
cancel by thread Id
th®Delete();
cancel thread and delete th when cancel finished.
Deleting of the thread instance by the operator delete is dangerous. Use th®Delete instead. C++ delete is safe only if thread is not running.

Often during the canceling some clean up actions must be made. To define clean up functions a user can call:

void UserCleanUp(void *arg){
        // here the user cleanup is done
        ...
}

TThread::CleanUpPush(&UserCleanUp,arg); // push user function 
                                        // into cleanup stack
                                        // ``last in, first out''

TThread::CleanUpPop(1); // pop user function out of stack
                        // and execute it,
                        // thread resumes after this call

TThread::CleanUpPop(0); // pop user function out of stack
                        // _without_ executing it
Note: CleanUpPush and CleanUpPop should be used as corresponding pairs like brackets; unlike Pthreads cleanup stack (which is not implemented here), TThread does not force this usage.

5.6  Finishing thread

When a thread returns from a user function the thread is finished. But it also can be finished by TThread::Exit(). Then, in case of Pthread ``detached'' mode, the thread vanishes completely.

By default, on finishing TThread executes the most recent cleanup function (CleanUpPop(1) is called automatically once).

6  Advanced: TThreads with classes

6.1  How to launch a class memberfunction as TThread

Consider a class Myclass with a memberfunction void* Myclass::Thread0((void* arg) that shall be launched as a thread. To start Thread0 as a TThread, class Myclass may provide a method:
Int_t Myclass::Threadstart(){
 if(!mTh){
     mTh= new TThread("memberfunction",
                      (void(*) (void *))&Thread0,
                      (void*) this);
     mTh->Run();
     return 0;
     }
 return 1;
}
Here mTh is a TThread* pointer which is member of Myclass (should be initialized to 0 in the constructor). TThread constructor is called as for a plain C function (see section 4.1), except for some differences:
 
  1. Memberfunction Thread0 requires an explicit cast to (void(*) (void *)) (this may cause a compiler warning like:

  2. Myclass.cxx:98: warning: converting from `void (Myclass::*)(void *)' to `void *' ), which is annoying but harmless.
  3. Strictly speaking, Thread0 must be a static member function to be called from a thread. Some compilers, for example gcc version 2.95.2, may not allow the (void(*) (void*))s cast and just stop if Thread0 is not static. On the other hand, if Thread0 is static, no compiler warnings are generated at all. Because the 'this' pointer is passed in 'arg' in the call to Thread0(void *arg), you have access to the instance of the class even if Thread0 is static. Using the 'this' pointer, non static members can still be read and written from Thread0, as long as you have provided Getter and Setter methods for these members.
    For example:
    Bool_t state = arg->GetRunStatus();
    arg->SetRunStatus(state);
Second, the pointer to the current instance of Myclass, i.e. (void*) this, has to be passed as first argument of the threaded function Thread0 (C++ member functions internally expect the this pointer as first argument to have access to class members of the same instance). pthreads are made for simple C functions and do not know about Thread0 being a member function of a class.  Thus, you have to pass this information by hand, if you want to access all members of the Myclass
instance from the Thread0 function.
Note: Method Thread0 must not be a virtual memberfunction, since the cast of Thread0 to void(*) in TThread constructor may raise problems with C++ virtual function table. However, Thread0 may call another virtual memberfunction virtual void Myclass::Func0() which then can be overridden in a derived class of Myclass. (see example 8.3).


Class Myclass may also provide a method to stop the running thread:

Int_t Myclass::Threadstop(){
 if(mTh){
        TThread::Delete(mTh);
        delete mTh;
        mTh=0;
        return 0;
 }      
 return 1;
}


Example 8.3: Class TThreadframe ( TThreadframe.h, TThreadframe.cxx) is a simple example of a framework class managing up to four threaded methods. Class TMhs3 (TMhs3.h, TMhs3.cxx) inherits from this base class, showing the mhs3 example 8.1 (mhs3.h, mhs3.cxx) within a class.

Makefile of example builds shared libraries libTThreadframe.so and libTMhs3.so. These are either loaded and executed by root macro TMhs3demo.C, or are linked against executable TMhs3run.cxx.

7  Thanks and Credits

To:
Victor Perevoztchikov
 
Fons Rademakers
 
Rene Brun
 
for their support in reimplementing and putting threads back to ROOT.

8  List of Example files

8.1  Example mhs3

8.2  Example conditions

8.3  Example TMhs3

8.4  Example CalcPiThread


File translated from TEX by TTH, version 2.30.
On 3 Mar 2000, 10:54.
Updated examples and chapter  6.1 on 26 September 2001  (J.A.)