Difference between revisions of "Documentation/DevGuide/AdvUNO/UNO Design Patterns and Coding Styles"
m |
|||
Line 19: | Line 19: | ||
The double-checked locking idiom is sometimes used in C/C++ code to speed up creation of a single-instance resource. In a ''multi-threaded'' environment, typical C++ code that creates a single-instance resource might look like the following example: | The double-checked locking idiom is sometimes used in C/C++ code to speed up creation of a single-instance resource. In a ''multi-threaded'' environment, typical C++ code that creates a single-instance resource might look like the following example: | ||
− | + | <syntaxhighlight lang="cpp"> | |
#include "osl/mutex.hxx" | #include "osl/mutex.hxx" | ||
Line 33: | Line 33: | ||
return pInstance; | return pInstance; | ||
} | } | ||
− | + | </syntaxhighlight> | |
A ''mutex'' guards against multiple threads simultaneously updating <code>pInstance</code>, and the nested static <code>aInstance</code> is guaranteed to be created only when first needed, and destroyed when the program terminates. | A ''mutex'' guards against multiple threads simultaneously updating <code>pInstance</code>, and the nested static <code>aInstance</code> is guaranteed to be created only when first needed, and destroyed when the program terminates. | ||
The disadvantage of the above function is that it must acquire and release the ''mutex'' every time it is called. The double-checked locking idiom was developed to reduce the need for locking, leading to the following modified function. Do not use.: | The disadvantage of the above function is that it must acquire and release the ''mutex'' every time it is called. The double-checked locking idiom was developed to reduce the need for locking, leading to the following modified function. Do not use.: | ||
− | + | <syntaxhighlight lang="cpp"> | |
#include "osl/mutex.hxx" | #include "osl/mutex.hxx" | ||
Line 54: | Line 54: | ||
return pInstance; | return pInstance; | ||
} | } | ||
− | + | </syntaxhighlight> | |
This version needs to acquire and release the ''mutex'' only when <code>pInstance</code> has not yet been initialized, resulting in a possible performance improvement. The ''mutex'' is still needed to avoid race conditions when multiple threads simultaneously see that <code>pInstance</code> is not yet initialized, and all want to update it at the same time. The problem with <code>getInstance2</code> is that it does not work. | This version needs to acquire and release the ''mutex'' only when <code>pInstance</code> has not yet been initialized, resulting in a possible performance improvement. The ''mutex'' is still needed to avoid race conditions when multiple threads simultaneously see that <code>pInstance</code> is not yet initialized, and all want to update it at the same time. The problem with <code>getInstance2</code> is that it does not work. | ||
Line 62: | Line 62: | ||
In C and C++, the problem can be solved, but only by using platform-specific instructions, typically some sort of memory-barrier instructions. There is a macro <code>OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER</code> in ''osl/doublecheckedlocking.h'' that uses the double-checked locking idiom in a way that actually works in C and C++. | In C and C++, the problem can be solved, but only by using platform-specific instructions, typically some sort of memory-barrier instructions. There is a macro <code>OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER</code> in ''osl/doublecheckedlocking.h'' that uses the double-checked locking idiom in a way that actually works in C and C++. | ||
− | + | <syntaxhighlight lang="cpp"> | |
#include "osl/doublecheckedlocking.h" | #include "osl/doublecheckedlocking.h" | ||
#include "osl/mutex.hxx" | #include "osl/mutex.hxx" | ||
Line 86: | Line 86: | ||
return p; | return p; | ||
} | } | ||
− | + | </syntaxhighlight> | |
The first (inner) use of <code>OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER</code> ensures that <code>aInstance</code>'s data has been written to main memory before <code>pInstance</code>'s data is written, therefore a thread can not see <code>pInstance</code> to be initialized when <code>aInstance</code>'s data has not yet reached main memory. This solves the problem described above. | The first (inner) use of <code>OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER</code> ensures that <code>aInstance</code>'s data has been written to main memory before <code>pInstance</code>'s data is written, therefore a thread can not see <code>pInstance</code> to be initialized when <code>aInstance</code>'s data has not yet reached main memory. This solves the problem described above. | ||
Line 94: | Line 94: | ||
If you are coding in C++, there is an easier way to use double-checked locking without worrying about the fine points. Use the rtl_Instance template from rtl/instance.hxx: | If you are coding in C++, there is an easier way to use double-checked locking without worrying about the fine points. Use the rtl_Instance template from rtl/instance.hxx: | ||
− | + | <syntaxhighlight lang="cpp"> | |
#include "osl/getglobalmutex.hxx" | #include "osl/getglobalmutex.hxx" | ||
#include "osl/mutex.hxx" | #include "osl/mutex.hxx" | ||
Line 115: | Line 115: | ||
Init(), ::osl::GetGlobalMutex()); | Init(), ::osl::GetGlobalMutex()); | ||
} | } | ||
− | + | </syntaxhighlight> | |
Note that an extra function class is required in this case. The documentation of rtl_Instance contains further examples of how this template can be used. | Note that an extra function class is required in this case. The documentation of rtl_Instance contains further examples of how this template can be used. | ||
Revision as of 17:27, 2 January 2021
- Differences Between UNO and Corba
- UNO Design Patterns and Coding Styles
This chapter discusses design patterns and coding recommendations for Apache OpenOffice. Possible candidates are:
- Singleton: global service manager, Desktop, UCB
- Factory: decouple specification and implementation, cross-environment instantiation, context-specific instances
- Listener: eliminate polling
- Element access: it is arguable if that is a design pattern or just an API
- Properties: solves remote batch access, but incurs the problem of compile-time type indifference
- UCB commands: universal dispatching of content specific operations
- Dispatch commands: universal dispatching of object specific operations, chain of responsibility
Double-Checked Locking
The double-checked locking idiom is sometimes used in C/C++ code to speed up creation of a single-instance resource. In a multi-threaded environment, typical C++ code that creates a single-instance resource might look like the following example:
#include "osl/mutex.hxx" T * getInstance1() { static T * pInstance = 0; ::osl::MutexGuard aGuard(::osl::Mutex::getGlobalMutex()); if (pInstance == 0) { static T aInstance; pInstance = &aInstance; } return pInstance; }
A mutex guards against multiple threads simultaneously updating pInstance
, and the nested static aInstance
is guaranteed to be created only when first needed, and destroyed when the program terminates.
The disadvantage of the above function is that it must acquire and release the mutex every time it is called. The double-checked locking idiom was developed to reduce the need for locking, leading to the following modified function. Do not use.:
#include "osl/mutex.hxx" T * getInstance2() { static T * pInstance = 0; if (pInstance == 0) { ::osl::MutexGuard aGuard(::osl::Mutex::getGlobalMutex()); if (pInstance == 0) { static T aInstance; pInstance = &aInstance; } } return pInstance; }
This version needs to acquire and release the mutex only when pInstance
has not yet been initialized, resulting in a possible performance improvement. The mutex is still needed to avoid race conditions when multiple threads simultaneously see that pInstance
is not yet initialized, and all want to update it at the same time. The problem with getInstance2
is that it does not work.
Assume that thread 1 calls getInstance2
first, finding pInstance
uninitialized. It acquires the mutex, creates aInstance
that results in writing data into aInstance
's memory, updates pInstance
that results in writing data into pIntance
's memory, and releases the mutex. Some hardware memory models a write the operations that transfer aInstance
's and pInstance
's data to main memory to be re-ordered by the processor executing thread 1. Now, if thread 2 enters getInstance2
when pInstance
's data has already been written to main memory by thread 1, but aInstance's data has not been written yet (remember that write operations may be done out of order), then thread 2 sees that pInstance
has already been initialized and exits from getInstance2
directly. Thread 2 dereferences pInstance
thereafter, accessing aInstance
's memory that has not yet been written into. Anything may happen in this situation.
In Java, double-checked locking can never be used, because it is broken and cannot be fixed.
In C and C++, the problem can be solved, but only by using platform-specific instructions, typically some sort of memory-barrier instructions. There is a macro OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER
in osl/doublecheckedlocking.h that uses the double-checked locking idiom in a way that actually works in C and C++.
#include "osl/doublecheckedlocking.h" #include "osl/mutex.hxx" T * getInstance3() { static T * p = 0; T * pInstance = p; if (p == 0) { ::osl::MutexGuard aGuard(osl::Mutex::getGlobalMutex()); p = pInstance; if (p == 0) { static T aInstance; p = &aInstance; OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); pInstance = p; } } else OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); return p; }
The first (inner) use of OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER
ensures that aInstance
's data has been written to main memory before pInstance
's data is written, therefore a thread can not see pInstance
to be initialized when aInstance
's data has not yet reached main memory. This solves the problem described above.
The second (outer) usage of OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER
is required to solve a problem concerning the reordering on Alpha processors.
For more information about this problem, see Reordering on an Alpha processor by Bill Pugh (www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html) and Pattern-Oriented Software Architecture, Volume 2: Patterns for Concurrent and Networked Objects by Douglas C. Schmidt et al (Wiley, 2000). Also see the Usenet article Re:Talking about volatile and threads synchronization by Davide Butenhof (October 2002) on why the outer barrier can be moved into an else clause. |
If you are coding in C++, there is an easier way to use double-checked locking without worrying about the fine points. Use the rtl_Instance template from rtl/instance.hxx:
#include "osl/getglobalmutex.hxx" #include "osl/mutex.hxx" #include "rtl/instance.hxx" namespace { struct Init() { T * operator()() { static T aInstance; return &aInstance; } }; } T * getInstance4() { return rtl_Instance< T, Init, ::osl::MutexGuard, ::osl::GetGlobalMutex >::create( Init(), ::osl::GetGlobalMutex()); }
Note that an extra function class is required in this case. The documentation of rtl_Instance contains further examples of how this template can be used.
If you are looking for more general information, the article The "Double-Checked Locking is Broken" Declaration (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) is a good source on double-checked locking, while Computer Architecture: A Quantitative Approach, Third Edition by John L. Hennessy and David A. Patterson (Morgan Kaufmann, 2002) and UNIX® Systems for Modern Architectures: Symmetric Multiprocessing and Caching for Kernel Programmers by Curt Schimmel (Addison-Wesley, 1994) offer detailed information about hardware memory models. |
Content on this page is licensed under the Public Documentation License (PDL). |