Difference between revisions of "Uno/Article/Multi-Thread Programming"

From Apache OpenOffice Wiki
< Uno
Jump to: navigation, search
(Replaced ABI with OBI.)
(Improved wording, fixed examples.)
Line 1: Line 1:
'''Preface''': The technology described in this article depends on the presence of the [[Uno/Effort/Creating the Uno Threading Framework|Uno Threading Framework]].
+
{{Uno/Note}} The technology described in this article depends on the presence of the [[Uno/Effort/Creating the Uno Threading Framework|Uno Threading Framework]].
  
 
[[Uno]] is inherently multi-threaded, every Uno object may be accessed by multiple threads concurrently. The [[Uno/Spec/Threading Model|Uno threading framework]] provides support for simplifying multi-thread programming.
 
[[Uno]] is inherently multi-threaded, every Uno object may be accessed by multiple threads concurrently. The [[Uno/Spec/Threading Model|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  
 
There are actually three things important to know about, when doing multi-threading and Uno. These are  
* dedicated thread related environments,
+
* the dedicated, thread related environments,
 
* how to use these environments when doing particular implementations,
 
* how to use these environments when doing particular implementations,
 
* and certainly, how to use threads WRT Uno objects.
 
* and certainly, how to use threads WRT Uno objects.
  
Environments, mappings and object are at the heart of Uno, please read [[../Working with Environments, Mappings & Objects]] for an introduction.
+
Environments, mappings and objects are at the heart of Uno, please read [[../Working with Environments, Mappings & Objects]] for an introduction.
  
 
==Environments==
 
==Environments==
Every [[Uno]] reference points to an object with particular characteristics. Among implementing a concrete interface and [[Uno/Term/Object Binary Interface|Object Binary Interface (OBI)]], the object may have one or multiple "purposes" associated with it. The [[Uno/Term/Object Binary Interface|OBI]] and the "purposes" are expressed in the objects managing environment, e.g. the environment described by <code>"gcc3:unsafe"</code> manages objects with a GCC3 C++ [[Uno/Term/Object Binary Interface|OBI]] (if named properly, it would have been called "g++3" or "gpp3"), which are [[Uno/Term/Thread Unsafe|thread-unsafe]].
+
Every [[Uno]] reference points to an object with particular characteristics. Among implementing a concrete interface and [[Uno/Term/Object Binary Interface|Object Binary Interface (OBI)]], the object may have one or multiple "purposes" associated with it. The [[Uno/Term/Object Binary Interface|OBI]] and the "purposes" are expressed in the descriptor of the objects managing environment, e.g. the environment described by <code>"gcc3:unsafe"</code> manages objects with a GCC3 C++ [[Uno/Term/Object Binary Interface|OBI]] (if named properly, it would have been called "g++3" or "gpp3"), which are [[Uno/Term/Thread Unsafe|thread-unsafe]].
  
The [[Uno/Spec/Threading Model|Uno threading model]] brings [[Uno/Spec/Thread Affinity Bridge|thread-affine purpose environments]] and [[Uno/Spec/Thread Unsafety Bridge|thread-unsafe purpose environments]]. Objects not belonging to one of these two [[Uno/Spec/Purpose Environment|purpose environments]] are assumed to be [[Uno/Term/Thread Safe|thread-safe]].  
+
The [[Uno/Spec/Threading Model|Uno threading model]] introduces [[Uno/Spec/Thread Affinity Bridge|thread-affine purpose environments]] and [[Uno/Spec/Thread Unsafety Bridge|thread-unsafe purpose environments]]. Objects not belonging to one of these two [[Uno/Spec/Purpose Environment|purpose environments]] are assumed to be [[Uno/Term/Thread Safe|thread-safe]].  
  
 
Examples:
 
Examples:
 
* <code>"gcc3:unsafe"</code> - Environment for managing objects with a GCC3 C++ [[Uno/Term/Object Binary Interface|OBI]], which are [[Uno/Term/Thread Unsafe|thread-unsafe]].
 
* <code>"gcc3:unsafe"</code> - Environment for managing objects with a GCC3 C++ [[Uno/Term/Object Binary Interface|OBI]], which are [[Uno/Term/Thread Unsafe|thread-unsafe]].
* <code>"java"</code> - Environment for managing Java JNI objects, without any further characteristics, therefor the managed objects have to be [[Uno/Term/Thread Safe|thread-safe]].
+
* <code>"java"</code> - Environment for managing Java JNI objects, no further characteristics have been specified, therefor the managed objects have to be [[Uno/Term/Thread Safe|thread-safe]].
 
* <code>"java:affine"</code> - Environment for managing Java JNI objects which are [[Uno/Term/Thread Affine|thread-affine]].
 
* <code>"java:affine"</code> - Environment for managing Java JNI objects which are [[Uno/Term/Thread Affine|thread-affine]].
  
Line 23: Line 23:
 
Any environment with an ":unsafe" in its description is a [[Uno/Term/Thread Unsafe|thread-unsafe]] environment. Objects managed by such an environment may not be called directly by multiple threads. See the specification of the [[Uno/Spec/Thread Unsafety Bridge|thread-unsafety bridge]] for details.
 
Any environment with an ":unsafe" in its description is a [[Uno/Term/Thread Unsafe|thread-unsafe]] environment. Objects managed by such an environment may not be called directly by multiple threads. See the specification of the [[Uno/Spec/Thread Unsafety Bridge|thread-unsafety bridge]] for details.
  
====C++ Example - Entering a thread-unsafe environment====
+
====C++ Example - Activating a thread-unsafe Environment====
 
The semantics of "entering" or "invoking" a [[Uno/Term/Thread Unsafe|thread-unsafe]] environment are the same.  
 
The semantics of "entering" or "invoking" a [[Uno/Term/Thread Unsafe|thread-unsafe]] environment are the same.  
 
<code>[cpp]
 
<code>[cpp]
Line 38: Line 38:
 
...
 
...
 
</code>
 
</code>
Basically, only one thread at a time can have activated any "<OBI>:unsafe" environment in this process.
+
Basically, only one thread at a time can have activated any "<OBI>:unsafe" environment in a particular process.
  
 
===Thread-Affine===
 
===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 multiple threads. See the specification of the [[Uno/Spec/Thread Affinity Bridge|thread-affinity bridge]] for details.
+
Any environment with an ":affine" in its description is a [[Uno/Term/Thread Affine|thread-affine]] environment. Objects managed by such an environment may not be called directly by multiple threads. See the specification of the [[Uno/Spec/Thread Affinity Bridge|thread-affinity bridge]] for details.
  
Actually, the semantics of "entering" or "invoking" a thread-affine environment differ. Entering a thread-affine environment is only possible, if no thread has been associated with this environment yet, if a thread has already been associated, the entering thread waits until the associated thread  leaves (e.g. terminates) the environment. The associated thread may leave the moment, the last managed object has been released. After the current thread has been associated 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 to be associated with it, if non has been associated yet, all invocations of objects are then dispatched to this (new) thread.
+
Actually, the semantics of "entering" or "invoking" a thread-affine environment differ. Entering a thread-affine environment is only possible, if no thread has been associated with this environment yet, if a thread has already been associated, the entering thread waits until the associated thread  leaves the environment. An associated thread may only leave a thread-affine environment, in case no object is managed (e.g. the last managed object has been released). Finally, the entering thread becomes the associated thread of the thread-affine environment. All invocations of objects of this thread-affine environment get dispatched into the associated thread.
  
====C++ Example - Entering a thread-affine environment====
+
In contrast, "invoking" a thread-affine environment creates a new, dedicated and hidden thread to be associated with it, if non has been associated yet, all invocations of objects or functions 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.
 
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.
  
Line 55: Line 57:
 
   cppu::EnvGuard affineGuard(Environment(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("gcc3:affine"))));
 
   cppu::EnvGuard affineGuard(Environment(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("gcc3:affine"))));
 
    
 
    
 +
  uno::Reference<XMultiServiceFactory> smgr(...);
 +
 
   smgr->createInstanceWithArguments(new MyUnoObject());
 
   smgr->createInstanceWithArguments(new MyUnoObject());
  
   // the implicit "leave" call blocks, until all objects managed by "gcc3:affine" are released.
+
   // The implicit leave of the "gcc3:affine" blocks, until all managed objects (MyUnoObjects) are released.
 
}
 
}
 
...
 
...
 
</code>
 
</code>
  
====C++ Example - Invoking a thread-affine environment====
+
====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.
+
This example shows, how to correctly invoke a thread-affine environment, as always, all objects need to be managed properly by the managing environments.
 
<code>[cpp]
 
<code>[cpp]
 
class MyUnoObject ...;
 
class MyUnoObject ...;
Line 95: Line 99:
 
* from the thread-safe C++ environment to the current (typically thread-unsafe or thread-affine) environment.
 
* from the thread-safe C++ environment to the current (typically thread-unsafe or thread-affine) environment.
  
Please have a look a the [[Uno/Cpp/Spec/Shield Helpers|specification for the shield helpers]] for more details.
+
Please have a look a the [[Uno/Cpp/Spec/Shield Helpers|shield helpers specification]] for more details.
  
 
=====C++ Example - Map Object to Thread-Safe=====
 
=====C++ Example - Map Object to Thread-Safe=====
Line 108: Line 112:
 
                                     curr2safe.mapInterface(
 
                                     curr2safe.mapInterface(
 
                                       pObject,  
 
                                       pObject,  
                                       getCppuType((cssu::Reference<XInterface> *)NULL)
+
                                       getCppuType((uno::Reference<uno::XInterface> *)NULL)
 
                                     )
 
                                     )
 
                                 );
 
                                 );
Line 132: Line 136:
 
                                     safe2curr.mapInterface(
 
                                     safe2curr.mapInterface(
 
                                       pObject,  
 
                                       pObject,  
                                       getCppuType((cssu::Reference<uno::XInterface> *)NULL)
+
                                       getCppuType((uno::Reference<uno::XInterface> *)NULL)
 
                                     )
 
                                     )
 
                                   );
 
                                   );
Line 196: Line 200:
  
 
==Objects==
 
==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
+
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
 
* [[Uno/Term/Thread Unsafe|thread-unsafe]], or
 
* [[Uno/Term/Thread Unsafe|thread-unsafe]], or
 
* [[Uno/Term/Thread Safe|thread-safe]], or
 
* [[Uno/Term/Thread Safe|thread-safe]], or
Line 212: Line 216:
 
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 [http://util.openoffice.org/source/browse/util/io/source/acceptor/ util/io/Acceptor] implementation.
 
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 [http://util.openoffice.org/source/browse/util/io/source/acceptor/ 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 [[Uno/Spec/Environment|environment]]. Basically ensuring a low inter-environment call frequency, converting any potential advantage of self synchronized methods to the reverse.
+
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 [[Uno/Spec/Environment|environment]], basically ensuring a low inter-environment call frequency, converting any potential advantage of self synchronized methods to the reverse.
  
'''Note:''' Scalability may be achieved by the introduction of [[Uno/Spec/Named Environment|named environments]], actually allowing any number of [[Uno/Term/Thread Unsafe|thread-unsafe]] [[Uno/Spec/Purpose Environment|purpose environments]] to exist simultaneously and to be activated by multiple threads in parallel.
+
'''Note:''' Scalability may be achieved by the introduction of [[../Working with Environments, Mappings & Objects#Naming|named environments]], actually allowing any number of [[Uno/Term/Thread Unsafe|thread-unsafe]] [[Uno/Spec/Purpose Environment|purpose environments]] to exist simultaneously and to be activated by multiple threads independently.
  
 
====Thread-Affine====
 
====Thread-Affine====
Line 220: Line 224:
  
 
===Implementation===
 
===Implementation===
Every type of object needs to be implemented somewhere. Dependent on the location, different actions need to be taken, to ensure correct usage of the object with respect to its threading architecture.
+
Every object needs to be implemented somewhere. Dependent on the location, different actions need to be taken, to ensure correct usage of the object with respect to its threading architecture.
  
 
====Components====
 
====Components====
The easiest way to implement an object is a component, as a component actively provides the managing environments of its objects. That means, that components do not need to ensure proper mapping etc., this is all taken care of by the component loader already.
+
The easiest way to implement an object is a component, as a component actively provides the managing environments of its objects. This means, that components do not need to ensure proper mapping etc., this is all taken care of by the component loader already.
  
=====C++ Example - A thread-unsafe component=====
+
=====C++ Example - A thread-unsafe Component=====
The <code>component_getImplementationEnvironment</code> function for a component does return the single managing environment for all objects provided by this component. The implementation of this function for a [[Uno/Term/Thread Unsafe|thread-unsafe]] component may look like this:
+
The <code>component_getImplementationEnvironment</code> function for a component does return the single managing environment for all objects provided by this component. The implementation of this function for [[Uno/Term/Thread Unsafe|thread-unsafe]] objects may look like this:
 
<code>[cpp]
 
<code>[cpp]
 
extern "C" void SAL_CALL component_getImplementationEnvironment(
 
extern "C" void SAL_CALL component_getImplementationEnvironment(
Line 237: Line 241:
 
</code>
 
</code>
  
=====C++ Example - A thread variable component=====
+
=====C++ Example - A thread variable Component=====
 
A component implementing [[Uno/Term/Thread Safe|thread-safe]] and [[Uno/Term/Thread Transparent|thread-transparent]] objects may want to utilize these capabilities by avoiding any mapping, this can be done by implementing the <code>component_getImplementationEnvironmentExt</code> function, instead of the <code>component_getImplementationEnvironment</code> function. The implementation of this function for a thread variable component may look like this:
 
A component implementing [[Uno/Term/Thread Safe|thread-safe]] and [[Uno/Term/Thread Transparent|thread-transparent]] objects may want to utilize these capabilities by avoiding any mapping, this can be done by implementing the <code>component_getImplementationEnvironmentExt</code> function, instead of the <code>component_getImplementationEnvironment</code> function. The implementation of this function for a thread variable component may look like this:
 
<code>[cpp]
 
<code>[cpp]
Line 257: Line 261:
 
Uno objects may as well be implemented in libraries or applications. Caller and callee must agree one the managing environment for passed or returned objects, to not break [[Uno/Term/Environment Integrity]].
 
Uno objects may as well be implemented in libraries or applications. Caller and callee must agree one the managing environment for passed or returned objects, to not break [[Uno/Term/Environment Integrity]].
  
All public Uno libraries do return appropriate objects, the implementations of the API are only partly (namely [[Uno/Term/Object Binary Interface|OBI]]) specialized and dynamically map the return or paramter objects.
+
All public Uno libraries do return appropriate objects, the implementations of the API are only [[Uno/Term/Object Binary Interface|OBI]]) specialized and dynamically map the return or parameter objects according to their purposes.
  
'''Note: ''' No convention, except documentation, has yet been introduced to identify any environment specialization of a function.
+
{{Uno/Note}} No convention, except documentation, has yet been introduced to identify any environment specialization of a function.
  
=====C++ Example - Function always returning a thread-safe object=====
+
=====C++ Example - Function always returning a thread-safe Object=====
The following example shows a function always returning an appropriate (correct [[Uno/Term/Object Binary Interface|OBI]] and purpose) object of type XInterface. For this function to work properly, the client must have activated the appropriate environment, as the <code>uno::Reference</code> is only partly (namely [[Uno/Term/Object Binary Interface|OBI]]) specialized.
+
The following example shows a function always returning a thread-safe, while the objects implementation itself is thread-unsafe. For this function to work properly, the client must have left any thread-unsafe environment.
  
 
Callee:
 
Callee:
Line 271: Line 275:
  
 
   // We may want to ensure that we are in the "c++" only environment.
 
   // We may want to ensure that we are in the "c++" only environment.
   ensure(uno::getCurrent().getTypeName() == rtl::OUString(RTL_CONSTASCII_PARAM("c++")));
+
   assert(uno::getCurrentEnvironment().getTypeName() == rtl::OUString(RTL_CONSTASCII_PARAM("c++")));
  
 
   // We may want to open a new scope, to ensure that "result_Obj" does
 
   // We may want to open a new scope, to ensure that "result_Obj" does
Line 281: Line 285:
  
 
     // This reference points to a "thread-unsafe" object.
 
     // This reference points to a "thread-unsafe" object.
     Reference<uno::XInterface> unsafeEnv_Obj(new MyUnsafeObject());
+
     uno::Reference<uno::XInterface> unsafeEnv_Obj(new MyUnsafeObject());
  
 
     // We may do some activations on "unsafeEnv_Obj".
 
     // We may do some activations on "unsafeEnv_Obj".
Line 293: Line 297:
 
   }
 
   }
  
   // Using "result_obj" is "safe" here.
+
   // Using "result_obj" is fine here.
 
   return result_Obj;
 
   return result_Obj;
 
}
 
}
Line 302: Line 306:
 
...
 
...
 
{
 
{
   // We leave all "purpose" environments here, as "create_threadSafeObject" returns
+
   // We just leave all "purpose" environments here, as "create_threadSafeObject" returns
 
   // "c++" (thread-safe) objects only.
 
   // "c++" (thread-safe) objects only.
 
   cppu::AntiEnvGuard antiGuard;
 
   cppu::AntiEnvGuard antiGuard;
Line 311: Line 315:
 
</code>
 
</code>
  
=====C++ Example - Function only accepting thread-safe parameters=====
+
=====C++ Example - Function only accepting thread-safe Parameters=====
 
In the following example, the called function gets a thread-safe parameter, which needs to be mapped appropriately to the <code>"c++:unsafe"</code> environment, to be able to pass a [[Uno/Term/Thread Unsafe|thread-unsafe]] object to the <code>set</code> method of the parameter. For the function to work properly, the client must be in the thread-safe environment.
 
In the following example, the called function gets a thread-safe parameter, which needs to be mapped appropriately to the <code>"c++:unsafe"</code> environment, to be able to pass a [[Uno/Term/Thread Unsafe|thread-unsafe]] object to the <code>set</code> method of the parameter. For the function to work properly, the client must be in the thread-safe environment.
  
Line 319: Line 323:
 
void setUnsafeObject(uno::Reference<...> const & rObj) {
 
void setUnsafeObject(uno::Reference<...> const & rObj) {
 
   // We may want to ensure that we are in the "c++" only environment.
 
   // We may want to ensure that we are in the "c++" only environment.
   ensure(uno::getCurrent().getTypeName() == rtl::OUString(RTL_CONSTASCII_PARAM("c++")));
+
   assert(uno::getCurrentEnvironment().getTypeName() == rtl::OUString(RTL_CONSTASCII_PARAM("c++")));
  
 
   // We now activate (enter) the "c++:unsafe" environment.
 
   // We now activate (enter) the "c++:unsafe" environment.
Line 326: Line 330:
  
 
   // We "unshield" the parameter.
 
   // We "unshield" the parameter.
   Reference<type> unsafeEnv_Obj.set(cppu::unshield(rObj), SAL_NO_ACQUIRE);
+
   uno::Reference<...> unsafeEnv_Obj.set(cppu::unshield(rObj), SAL_NO_ACQUIRE);
  
 
   // MyUnsafeObj has a C++ OBI and is thread-unsafe
 
   // MyUnsafeObj has a C++ OBI and is thread-unsafe
Line 351: Line 355:
 
* Asynchronous threads, which run in the thread-safe environment.
 
* Asynchronous threads, which run in the thread-safe environment.
 
* Synchronous threads, which run in a thread-unsafe or thread-affine environment.
 
* Synchronous threads, which run in a thread-unsafe or thread-affine environment.
* Hidden threads, which run in an objects implementation only.
+
* Hidden threads, which run behind an objects implementation only.
 
Mixed types are certainly possible.
 
Mixed types are certainly possible.
  
 
===Asynchronous===
 
===Asynchronous===
The asynchronous thread holds one or multiple references to thread-safe Uno objects. During its execution it may call on one or another of these objects. Every call may compete with any another threads call. In case a called object is not thread-safe (e.g. thread-unsafe or thread-affine), the appropriate environments becomes activated implicitly before the call, and becomes deactivated implicitly after the call.
+
The asynchronous thread holds one or multiple references to thread-safe Uno objects. During its execution it may call on one or another of these objects. Every call may compete with any another threads call to the same particular object. In case a called object is not thread-safe (e.g. thread-unsafe or thread-affine), the appropriate environment becomes activated respectively deactivated implicitly before and after the call.
  
 
===Synchronous===
 
===Synchronous===
The synchronous thread holds one or multiple references to thread-unsafe or thread-affine objects. Before actually invoking any calls, the thread does activate the managing environment of hold objects. The calls are therefor not competing and the call sequence is atomic. After a sequence of calls, the thread deactivates the managing environment.
+
The synchronous thread holds one or multiple references to thread-unsafe or thread-affine objects. Before actually invoking any object, the thread does activate the managing environment. The calls are therefor not competing with any other thread and the call sequence is atomic. After a sequence of calls, the thread deactivates the managing environment again.
  
 
===Hidden===
 
===Hidden===
Line 365: Line 369:
 
===C++ Examples===
 
===C++ Examples===
 
====Asynchronous Thread====
 
====Asynchronous Thread====
Do not activate any environment explicitly, just run in the thread-safe environment. Only implicitly activate other environments when activating mapped objects (e.g. thread-unsafe object).
+
Do not activate any environment explicitly, just run in the thread-safe environment. Only implicitly activate other environments when invoking mapped objects (e.g. thread-unsafe object).
  
 
<code>[cpp]
 
<code>[cpp]
Line 380: Line 384:
  
 
public:
 
public:
   MyThread(uno::Reference<XInterface> const & xInterface)
+
   MyThread(uno::Reference<...> const & object)
     : m_xInterface(cppu::shield(xInterface), SAL_NO_ACQUIRE)
+
     : m_safe_obj(cppu::shield(object), SAL_NO_ACQUIRE)
 
   {}
 
   {}
 
};
 
};
Line 394: Line 398:
 
   uno::Reference<...> m_unsafe_obj;
 
   uno::Reference<...> m_unsafe_obj;
  
   static void s_doSomething(va_list param)
+
   static void s_clear(va_list param)
 
   {
 
   {
 
     MyThread * pMyThread = va_arg(param, MyThread *);
 
     MyThread * pMyThread = va_arg(param, MyThread *);
Line 411: Line 415:
  
 
public:
 
public:
   MyThread(uno::Reference<XInterface> const & xInterface);
+
   MyThread(uno::Reference<...> const & object);
     : m_xInterface(xInterface), m_refEnv(uno::getCurrentEnvironment());
+
     : m_unsafe_obj(object), m_refEnv(uno::getCurrentEnvironment());
 
   {}
 
   {}
  
Line 422: Line 426:
 
   }
 
   }
  
   void doSomething()
+
   virtual void SAL_CALL run()
 
   {
 
   {
 
     m_refEnv.invoke(s_doSomething, this);
 
     m_refEnv.invoke(s_doSomething, this);

Revision as of 14:48, 1 September 2006

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

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

  • the 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 objects 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 Object Binary Interface (OBI), the object may have one or multiple "purposes" associated with it. The OBI and the "purposes" are expressed in the descriptor of the objects managing environment, e.g. the environment described by "gcc3:unsafe" manages objects with a GCC3 C++ OBI (if named properly, it would have been called "g++3" or "gpp3"), which are thread-unsafe.

The Uno threading model introduces 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++ OBI, which are thread-unsafe.
  • "java" - Environment for managing Java JNI objects, no further characteristics have been specified, 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 multiple threads. See the specification of the thread-unsafety bridge for details.

C++ Example - Activating a thread-unsafe Environment

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

 // 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

} ... Basically, only one thread at a time can have activated any "<OBI>:unsafe" environment in a particular process.

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 multiple 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 is only possible, if no thread has been associated with this environment yet, if a thread has already been associated, the entering thread waits until the associated thread leaves the environment. An associated thread may only leave a thread-affine environment, in case no object is managed (e.g. the last managed object has been released). Finally, the entering thread becomes the associated thread of the thread-affine environment. All invocations of objects of this thread-affine environment get dispatched into the associated thread.

In contrast, "invoking" a thread-affine environment creates a new, dedicated and hidden thread to be associated with it, if non has been associated yet, all invocations of objects or functions 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.

[cpp] class MyUnoObject ...;

... {

 cppu::EnvGuard affineGuard(Environment(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("gcc3:affine"))));
 
 uno::Reference<XMultiServiceFactory> smgr(...);
 smgr->createInstanceWithArguments(new MyUnoObject());
 // The implicit leave of the "gcc3:affine" blocks, until all managed objects (MyUnoObjects) are released.

} ...

C++ Example - Invoking a thread-affine Environment

This example shows, how to correctly invoke a thread-affine environment, as always, all objects need to be managed properly by the managing environments. [cpp] 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

Any environment with neither ":unsafe" nor ":affine" in its description is a thread-safe environment. Objects managed by such an environment may very well be called directly by concurrent threads. Examples for thread-safe environments are "gcc3" or "java", and also "gcc3:debug" or "uno:debug".

Helpers

C++ Shield Helpers

The "shield" helpers basically allow to shortcut the mapping of an object

  • from the current (typically thread-unsafe or thread-affine) environment, to the thread-safe C++ environment,
  • from the thread-safe C++ environment to the current (typically thread-unsafe or thread-affine) environment.

Please have a look a the shield helpers specification for more details.

C++ Example - Map Object to Thread-Safe

Do the mapping by hand: [cpp] ... uno::XInterface * pUnsafe_Object ...; uno::Mapping curr2safe(uno::getCurrentEnvironment(),

                      rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(CPPU_STRINGIFY(CPPU_ENV))));

uno::XInterface * pSafe_Object = reinterpret_cast<uno::XInterface *>(

                                   curr2safe.mapInterface(
                                     pObject, 
                                     getCppuType((uno::Reference<uno::XInterface> *)NULL)
                                   )
                                );

... Can be simply replaced with: [cpp] ... uno::XInterface * pUnsafe_Object ...; uno::XInterface * pSafe_Object = cppu::shield(pUnsafe_Object); ...

C++ Example - Map Object from Thread-Safe

Do the mapping by hand: [cpp] ... uno::XInterface * pSafe_Object ...; uno::Mapping safe2curr(rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(CPPU_STRINGIFY(CPPU_ENV))),

                      uno::getCurrentEnvironment());

uno::XInterface * pUnsafe_Object = reinterpret_cast<uno::XInterface *>(

                                    safe2curr.mapInterface(
                                      pObject, 
                                      getCppuType((uno::Reference<uno::XInterface> *)NULL)
                                    )
                                  );

... Can be simply replaced with: [cpp] ... uno::XInterface * pSafe_Object ...; uno::XInterface * pUnsafe_Object = cppu::unshield(pSafe_Object); ...

C++ Example - Map uno::Any to Thread-Safe

Do the mapping by hand: [cpp] ... uno::Any unsafeAny = ...

uno::Mapping curr2safe(uno::getCurrentEnvironment(),

                      rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(CPPU_STRINGIFY(CPPU_ENV))));

uno::Any safeAny; uno_any_destruct(&safeAny, (uno_ReleaseFunc)uno::cpp_release); uno_type_any_constructAndConvert(&safeAny,

                                const_cast<void *>(unsafeAny.getValue()),
                                unsafeAny.getValueTypeRef(),
                                curr2safe.get());

... Can be simply replaced with: [cpp] ... uno::Any unsafeAny = ... uno::Any safeAny(cppu::shieldAny(unsafeAny)); ...

C++ Example - Map uno::Any from Thread-Safe

Do the mapping by hand: [cpp] ... uno::Any safeAny = ...

uno::Mapping safe2curr(uno::getCurrentEnvironment(),

                      rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(CPPU_STRINGIFY(CPPU_ENV))));

uno::Any unsafeAny; uno_any_destruct(&unsafeAny, (uno_ReleaseFunc)uno::cpp_release); uno_type_any_constructAndConvert(&unsafeAny,

                                const_cast<void *>(safeAny.getValue()),
                                safeAny.getValueTypeRef(),
                                safe2curr.get());

... Can be simply replaced with: [cpp] ... uno::Any safeAny = ... uno::Any unsafeAny(cppu::unshieldAny(safeAny)); ...

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

Architecture

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
  • you want to avoid any 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 any 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 simultaneously and to be activated by multiple threads independently.

Thread-Affine

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

Implementation

Every object needs to be implemented somewhere. Dependent on the location, different actions need to be taken, to ensure correct usage of the object with respect to its threading architecture.

Components

The easiest way to implement an object is a component, as a component actively provides the managing environments of its objects. This means, that components do not need to ensure proper mapping etc., this is all taken care of by the component loader already.

C++ Example - A thread-unsafe Component

The component_getImplementationEnvironment function for a component does return the single managing environment for all objects provided by this component. The implementation of this function for thread-unsafe objects may look like this: [cpp] extern "C" void SAL_CALL component_getImplementationEnvironment(

 sal_Char        const ** ppEnvTypeName, 
 uno_Environment       ** ppEnv

) {

 *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME ":unsafe";

}

C++ Example - A thread variable Component

A component implementing thread-safe and thread-transparent objects may want to utilize these capabilities by avoiding any mapping, this can be done by implementing the component_getImplementationEnvironmentExt function, instead of the component_getImplementationEnvironment function. The implementation of this function for a thread variable component may look like this: [cpp] extern "C" void SAL_CALL component_getImplementationEnvironmentExt(

 sal_Char        const ** ppEnvTypeName, 
 uno_Environment       ** ppEnv,
 sal_Char        const  * pImplName,
 uno_Environment        * pSrcEnv

) {

 rtl::OUString envName(RTL_CONSTASCII_USTRINGPARAM(CPPU_CURRENT_LANGUAGE_BINDING));
 envName += cppu::EnvDcp_getPurpose(Environment(pSrcEnv).getTypeName());
 uno_getEnvironment(ppEnv, envName.pData, NULL);

}

Libraries&Applications

Uno objects may as well be implemented in libraries or applications. Caller and callee must agree one the managing environment for passed or returned objects, to not break Uno/Term/Environment Integrity.

All public Uno libraries do return appropriate objects, the implementations of the API are only OBI) specialized and dynamically map the return or parameter objects according to their purposes.

Note: No convention, except documentation, has yet been introduced to identify any environment specialization of a function.

C++ Example - Function always returning a thread-safe Object

The following example shows a function always returning a thread-safe, while the objects implementation itself is thread-unsafe. For this function to work properly, the client must have left any thread-unsafe environment.

Callee: [cpp] // This function is environment specialized on "c++". uno::Reference<uno::XInterface> create_threadSafeObject(void) {

 uno:Reference<uno::XInterface>  result_Obj;
 // We may want to ensure that we are in the "c++" only environment.
 assert(uno::getCurrentEnvironment().getTypeName() == rtl::OUString(RTL_CONSTASCII_PARAM("c++")));
 // We may want to open a new scope, to ensure that "result_Obj" does
 // not get destructed while "c++:unsafe" is active.
 {
   // We activate (enter) the "c++:unsafe" environment.
   // Note: Any other environment suiteable for "MyUnsafeObject" would work as well.
   cppu::EnvGuard unsafeGuard(uno::Environment(rtl::OUString(RTL_CONSTASCII_PARAM("c++:unsafe"))));
   // This reference points to a "thread-unsafe" object.
   uno::Reference<uno::XInterface> unsafeEnv_Obj(new MyUnsafeObject());
   // We may do some activations on "unsafeEnv_Obj".
   unsafeEnv_Obj->doThis();
   unsafeEnv_Obj->doThat();
   // We "shield" the object and assign it to "result_Obj"
   result_Obj.set(cppu::shield(unsafeEnv_Obj), SAL_NO_ACQUIRE);
   // We may _not_ activate result_obj, as we are still in the "c++:unsafe" environment.
 }
 // Using "result_obj" is fine here.
 return result_Obj;

}

Caller: [cpp] ... {

 // We just leave all "purpose" environments here, as "create_threadSafeObject" returns
 // "c++" (thread-safe) objects only.
 cppu::AntiEnvGuard antiGuard;
 uno::Reference safe_obj(create_threadSafeObject());

} ...

C++ Example - Function only accepting thread-safe Parameters

In the following example, the called function gets a thread-safe parameter, which needs to be mapped appropriately to the "c++:unsafe" environment, to be able to pass a thread-unsafe object to the set method of the parameter. For the function to work properly, the client must be in the thread-safe environment.

Callee: [cpp] // This function is environment specialized on "c++". void setUnsafeObject(uno::Reference<...> const & rObj) {

 // We may want to ensure that we are in the "c++" only environment.
 assert(uno::getCurrentEnvironment().getTypeName() == rtl::OUString(RTL_CONSTASCII_PARAM("c++")));
 // We now activate (enter) the "c++:unsafe" environment.
 // Note: Any other environment suiteable for "MyUnsafeObject" would work as well.
 cppu::EnvGuard unsafeGuard(uno::Environment(rtl::OUString(RTL_CONSTASCII_PARAM("c++:unsafe"))));
 // We "unshield" the parameter.
 uno::Reference<...> unsafeEnv_Obj.set(cppu::unshield(rObj), SAL_NO_ACQUIRE);
 // MyUnsafeObj has a C++ OBI and is thread-unsafe
 unsafeEnv_Obj->set(new MyUnsafeObject());

}

Caller: [cpp] ... {

 // We leave all "purpose" environments here, as "setUnsafeObject" accepts
 // "c++" (thread-safe) objects only.
 cppu::AntiEnvGuard antiGuard;
 uno::Reference<...> obj(...);
 setUnsafeObj(obj);

} ...

Threads

Thinking about threads, thread related environments and Uno objects, we roughly can identify the following types:

  • Asynchronous threads, which run in the thread-safe environment.
  • Synchronous threads, which run in a thread-unsafe or thread-affine environment.
  • Hidden threads, which run behind an objects implementation only.

Mixed types are certainly possible.

Asynchronous

The asynchronous thread holds one or multiple references to thread-safe Uno objects. During its execution it may call on one or another of these objects. Every call may compete with any another threads call to the same particular object. In case a called object is not thread-safe (e.g. thread-unsafe or thread-affine), the appropriate environment becomes activated respectively deactivated implicitly before and after the call.

Synchronous

The synchronous thread holds one or multiple references to thread-unsafe or thread-affine objects. Before actually invoking any object, the thread does activate the managing environment. The calls are therefor not competing with any other thread and the call sequence is atomic. After a sequence of calls, the thread deactivates the managing environment again.

Hidden

The hidden thread is an implementation detail of a particular object only. Proper synchronization (e.g. acquiring / releasing mutexes) is taken care of by the implementer.

C++ Examples

Asynchronous Thread

Do not activate any environment explicitly, just run in the thread-safe environment. Only implicitly activate other environments when invoking mapped objects (e.g. thread-unsafe object).

[cpp] class MyThread : public Thread {

 uno::Reference<...> m_safe_obj; // this points to a "thread-safe" object

protected:

 virtual void SAL_CALL run()
 {
   m_safe_obj.doThis();
   m_safe_obj.doThat();
 }

public:

 MyThread(uno::Reference<...> const & object)
   : m_safe_obj(cppu::shield(object), SAL_NO_ACQUIRE)
 {}

};

Synchronous Thread

Just enter the environment and do all calls while being in it. Obviously, releasing the objects also needs to be done in the environment. [cpp] class MyThread : public Thread {

 uno::Environment    m_refEnv;
 uno::Reference<...> m_unsafe_obj;
 static void s_clear(va_list param)
 {
   MyThread * pMyThread = va_arg(param, MyThread *);
   pMyThread->m_unsafe_obj.clear();
 }
 static void s_doSomething(va_list param)
 {
   MyThread * pMyThread = va_arg(param, MyThread *);
   // do not do any slow/blocking operations here, as the target environment is
   // currently activated, and no other thread may enter at the moment...
   m_unsafe_obj->doThis();
   m_unsafe_obj->doThat();  
   pMyThread->i_doSomething();
 }

public:

 MyThread(uno::Reference<...> const & object);
   : m_unsafe_obj(object), m_refEnv(uno::getCurrentEnvironment());
 {}
 MyThread::~MyThread() 
 {
   // the object needs to be released in the managing environment.
   // unfortunately, there is not yet a SAL_NO_RELEASE
   m_refEnv.invoke(s_clear, this);
 }
 virtual void SAL_CALL run()
 {
   m_refEnv.invoke(s_doSomething, this);
 }

};

Specifications

The relevant specifications can be found here:

In particular:

See also

Personal tools