UNO Design Patterns and Coding Styles

From Apache OpenOffice Wiki
Jump to: navigation, search



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.

Documentation note.png For more information about this problem, see Reordering on an Alpha processor by Bill Pugh (https://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.

Documentation note.png If you are looking for more general information, the article The "Double-Checked Locking is Broken" Declaration (https://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).
Personal tools
In other languages