Uno/Article/Multi-Thread Programming

From Apache OpenOffice Wiki
< Uno
Revision as of 11:33, 3 August 2006 by Kr (Talk | contribs)

Jump to: navigation, search

Preface

The technology described in this article depends on the presence of the Uno Threading Framework.

Multi-Thread Programming

Uno is inherently multi-threaded, every Uno object may be accessed by multiple threads concurrently. The Uno threading framework provides support for simplifying multi-thread programming.

There are actually three things important to know about, when doing multi-threading and Uno. These are

  • dedicated thread related environments,
  • how to use these environments when doing particular implementations,
  • and certainly, how to use threads WRT Uno objects.

Environments, mappings and object are at the heart of Uno, please read Uno/Article/Working with Environments, Mappings & Objects for an introduction.

Environments

Every Uno reference points to an object with particular characteristics. Among implementing a concrete interface and ABI, the object may have one or multiple "purposes" associated with it. The ABI and the "purposes" are expressed in the objects managing environment, e.g. the environment described by "gcc3:unsafe" manages objects with a GCC3 C++ ABI (if named properly, it would have been called "g++3" or "gpp3"), which are thread-unsafe.

The Uno threading model brings thread-affine purpose environments and thread-unsafe purpose environments. Objects not belonging to one of these two purpose environments are assumed to be thread-safe.

Examples:

  • "gcc3:unsafe" - Environment for managing objects with a GCC3 C++ ABI, which are thread-unsafe.
  • "java" - Environment for managing Java JNI objects, without any further characteristics, therefor the managed objects have to be thread-safe.
  • "java:affine" - Environment for managing Java JNI objects which are thread-affine.

Thread-Unsafe

Any environment with an ":unsafe" in its description is a thread-unsafe environment. Objects managed by such an environment may not be called directly by concurrent threads. See the specification of the thread-unsafety bridge for details.

C++ Example - Entering a thread-unsafe environment

The semantics of "entering" or "invoking" a thread-unsafe environment are the same.

  ...
  {
    // Enter the "gcc3:unsafe" environment
    cppu::EnvGuard unsafeGuard(Environment(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("gcc3:unsafe"))));
    // Now we can safely directly call on any object belonging to this environment,
    // no second thread can enter this environment in parallel
    pObj->doSomething();

    // We implicity leave the "gcc3:unsafe" environment
  }
  ...

Thread-Affine

Any environment with an ":affine" in its description is a thread-affine environment. Objects managed by such an environment may not be called directly by concurrent threads. See the specification of the thread-affinity bridge for details.

Actually, the semantics of "entering" or "invoking" a thread-affine environment differ. Entering a thread-affine environment associates the current thread with this particular environment, all invocations of objects of this thread-affine environment get dispatched into this thread. In contrast, "invoking" a thread-affine environment creates a new, dedicated and hidden thread associated with it, all invocations of objects are then dispatched to this new thread.

C++ Example - Entering a thread-affine environment

In the following example, the newly created instance of "MyUnoObject" is guaranteed to only be called by the creating thread. When trying to leave the thread-affine environment, the d'tor of the "affineGuard" will block as long as objects are managed by this environment, basically ensuring that the objects are still reachable.

  class MyUnoObject ...;

  ...
  {
    cppu::EnvGuard affineGuard(Environment(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("gcc3:affine"))));
  
    smgr->createInstanceWithArguments(new MyUnoObject());

    // the implicit "leave" call blocks, until all objects managed by "gcc3:affine" are released.
  }
  ...
C++ Example - Invoking a thread-affine environment

The example shows, how to correctly invoke a thread-affine environment, as always, all objects need to be managed properly by the managing environments.

  class MyUnoObject ...;

  void doSomething(va_list param)
  {
    XMultiServiceFactory * pSmgr = va_arg(param, XMultiServiceFactory *);
    pSmgr->createInstanceWithArguments(new MyUnoObject());
  }

  ...
  {
    uno::Environment affineEnv(Environment(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("gcc3:affine"))));

    Mapping curr2affine(uno::getCurrentEnvironment(), affineEnv);

    void * affineSmgr = curr2affine.mapInterface(smgr, typeof(smgr));
    affineEnv.invoke(s_doSomething, affineSmgr);
    affineEnv.get()->pExtEnv->releaseInterface(affineSmgr);
  }
  ...

Thread-Safe

Objects

Going to implement an UNO object, you need to decide on the threading architecture. You basically have the following choices, the object can either be

Thread-Unsafe

Thread unsafe is the choice for most cases. Actually leaving proper synchronization of method calls to the runtime.

Thread-Safe

There are only rare cases where you actually want to implement your object thread safe. Either

  • your object should or must allow the parallel execution of some of its methods, or
  • your object wants to avoid the overhead associated with leaving synchronization to the runtime.

One case, where your component must allow the parallel execution of methods is, when you want to be able to abort a running invocation. UNO currently does not offer a mechanism to do this generically, so that particular objects must provide dedicated methods for abortion. An example for this is the util/io/Acceptor implementation.

The overhead for automatic synchronization only affects inter-environment calls. The threading architecture of a particular application should be designed in a way, that closely connected objects happen to exist in the same environment. Basically ensuring a low inter-environment call frequency, converting the potential advantage of self synchronized methods to the reverse.

Note: Scalability may be achieved by the introduction of named environments, actually allowing any number of thread-unsafe purpose environments to exist simultanesously and to be entered by multiple threads in parallel.

Thread-Affine

Thread-affine objects are rare. In OOo they are needed to encapsulate the Win32 respectively the OLE/COM thread affinity.

Threads

You may create threads in your implementation. Depending on the selected threading architecture, these threads may are allowed to be visible from the outside or not. If your implementation is to be instantiated in a

Obviously, invisible threads can be used and created anytime anyway. Invisible threads do not harm thread-transparency by definition.

Unsafe / Affine Code

If you are writing thread-unsafe or thread-affine code, any created thread to run outside the current (thread-unsafe / -affine) environment. The objects, which should be used by this thread, need to be mapped to the threads environment, which typically will be the thread-safe environment.

Mapped objects need to be managed by the belonging enviroment only, as long as this environment has not been entered explicity, otherwise methods (e.g. release or acquire) of the objects may be called from the wrong environment.

C++

Do not enter the environment explicity, but only implicity when calling methods on mapped objects (assuming the objects have been implemented in the target environment).

Use the shield helper:

class MyThread : public Thread 
{
  uno::Reference<XInterface> m_xInterface;

public:
  MyThread(uno::Reference<XInterface> const & xInterface)
    : m_xInterface(uno::shield(xInterface), SAL_NO_ACQUIRE)
  {
  }
};

Do the mapping by hand:

class MyThread : public Thread 
{
  uno::Reference<XInterface> m_xInterface;

public:
  MyThread(uno::Reference<XInterface> const & xInterface);
};

MyThread::MyThread(uno::Reference<XInterface> const & xInterface)
{
  uno::Mapping unsafe2safe(uno::getCurrentEnvironment(), 
                           rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(CPPU_STRINGIFY(CPPU_ENV))));
		
  m_xInterface.set(unsafe2safe.mapInterface(pT, getCppuType(m_xInterface)), 
		   SAL_NO_ACQUIRE);
}

Just enter the environment add do all calls while being in it. Obviously, releasing the objects also needs to be done in the environment.

class MyThread : public Thread 
{
  uno::Environment           m_refEnv;
  uno::Reference<XInterface> m_xInterface;

  void i_doSomething();

public:
  MyThread(uno::Reference<XInterface> const & xInterface);

  void doSomething();
};

MyThread::MyThread(uno::Reference<XInterface> const & xInterface)
 : m_xInterface(xInterface), m_refEnv(uno::getCurrentEnvironment());
{
}

void MyThread::doSomething()
{
  // do not do any slow/blocking operations here, as the target environment is
  // currently entered, and no other thread may enter at the moment...
  m_xInterface->...
}

static void s_doSomething(va_list param)
{
  MyThread * pMyThread = va_arg(param, MyThread *);
  pMyThread->i_doSomething();
}

void MyThread::doSomething()
{
  m_refEnv.invoke(s_doSomething, this);
}

MyThread::~MyThread() 
{
  m_refEnv.invoke(clearRefs);
}

These may be eased by using the FreeReference.

Free References

For some language bindings, there is support for shareable or free references (optional, e.g. Uno/Cpp/Spec/FreeReference). These free references may be used in any particular purpose environment, as they are taking care of the appropriate mapping of any objects prior to the invokation of a particular method.

C++
class MyUnoObject;

class MyObject 
{
  cppu::FreeReference<uno::XInterface> m_obj;

public:
  MyObject(XInterface * xInterface)
    : m_obj(xInterface, SAL_NO_ACQUIRE)
  {}

  void doSomething() 
  {
    m_obj.aMethod();
  }
};

class MyThread : public Thread 
{
  MyObject * m_pMyObject;
public:
  MyThread(MyObject * pMyObject);

  void run() 
  {
    m_pMyObject->doSomething();
  }
};

...
MyObject * pMyObject = new MyObject(new UnoObject());
MyThread * pMyThread = new pMyThread(pMyObject);

pMyObject->doSomething();
...


Specifications

The relevant specifications can be found here:

In particular:

See also

Personal tools