blob: 6f088f0218a2139149c9783f1fd33605ca256da4 [file] [log] [blame] [view]
---
layout: default
title: Custom ICU4C Synchronization
nav_order: 3
parent: Misc
---
<!--
© 2020 and later: Unicode, Inc. and others.
License & terms of use: http://www.unicode.org/copyright.html
-->
# Custom ICU4C Synchronization
{: .no_toc }
## Contents
{: .no_toc .text-delta }
1. TOC
{:toc}
---
> :warning: ***Support for including an alternate implementation of atomic and mutex
> operations has been withdrawn and removed from ICU4C.***
> See issue [ICU-20185](https://unicode-org.atlassian.net/browse/ICU-20185).
### Build Time User Provided Synchronization
Build time user synchronization provides a mechanism for platforms with special
requirements to provide their own mutex and one-time initialization
implementations to ICU. This facility was introduced in ICU 53. It may change
over time.
The alternative implementations are compiled directly into the ICU libraries.
Alternative implementations cannot be plugged in at run time.
The tables below show the items that must be defined by a custom ICU
synchronization implementation. The list includes both functions that are used
throughout ICU code and additional functions are for internal by other ICU
synchronization primitives.
**Low Level Atomics**, a set of platform or compiler dependent typedefs and
inlines. Provided in the internal header file
[`umutex.h`](https://github.com/unicode-org/icu/blob/master/icu4c/source/common/umutex.h).
| Type/Function | Description |
|---------------------------------------------------------|-------------------------------------------------------------------------------|
| `typedef u_atomic_int32_t` | A 32 bit integer that will work with low level atomic operations. (`typedef`) |
| `umtx_loadAcquire(u_atomic_int32_t &var)` | |
| `umtx_storeRelease(u_atomic_int32_t &var, int32_t val)` | |
| `umtx_atomic_inc(u_atomic_int32_t &var)` | |
| `umtx_atomic_dec(u_atomic_int32_t &var)` | |
**Mutexes**. Type declarations for ICU mutex wrappers. Provided in a header file.
| Type | Description |
|-----------------------|---------------------------------------------------------------------------------------------------|
| `struct UMutex` | An ICU mutex. All instances will be static. Typically just contains an underlying platform mutex. |
| `U_MUTEX_INITIALIZER` | A C style initializer for a static instance of a `UMutex`. |
**Mutex and InitOnce implementations**. Out-of-line platform-specific code.
Provided in a .cpp file.
| Function | Description |
|-----------------------------------------|--------------------------------------------|
| `umtx_lock(UMutex *mutex)` | Lock a mutex. |
| `umtx_unlock(UMutex* mutex)` | Unlock a mutex. |
| `umtx_initImplPreInit(UInitOnce &uio)` | `umtx_initOnce()` implementation function. |
| `umtx_initImplPostInit(UInitOnce &uio)` | `umtx_initOnce()` implementation function. |
`UInitOnce` and `umtx_initOnce()` are used internally by ICU for thread-safe
one-time initialization. Their implementation is split into a
platform-independent part (contained in
[`umutex.h`](https://github.com/unicode-org/icu/blob/master/icu4c/source/common/umutex.h)),
and the pair of platform-dependent implementation functions listed above.
**Build Setup**
Compiler preprocessor variables are used to name the custom files to be included
in the ICU build. If defined, the files are included at the top of the normal
platform `#ifdef` chains in the ICU sources, and effectively define a new
platform.
| Macro | Description |
|--------------------|---------------------------------------------------------|
| `U_USER_ATOMICS_H` | Set to the name of the low level atomics header file. |
| `U_USER_MUTEX_H` | Mutexes header file. |
| `U_USER_MUTEX_CPP` | Mutexes and `InitOnce` implementation file. |
It is possible (and reasonable) to supply only the two mutex files, while
retaining the ICU default implementation for the low level atomics.
Example ICU configure with user mutexes specified:
CPPFLAGS='-DU_USER_ATOMICS_H=atomic_c11.h -DU_USER_MUTEX_H=mutex_c11.h -DU_USER_MUTEX_CPP=mutex_c11.cpp' ./runConfigureICU --enable-debug Linux
**Stability**
This interface may change between ICU releases. The required set of functions
may be extended, or details of the behavior required may be altered.
The types and functions defined by this interface reach deeply into the ICU
implementation, and we need to retain the ability to make changes should the
need arise.
**Examples**
The code below shows a complete set of ICU user synchronization files.
This implementation uses C++11 language mutexes and atomics. These make for a
convenient reference implementation because the C++11 constructs are well
defined and straight forward to use.
Similar implementations for POSIX and Windows can be found in files
`common/umutex.h` and `common/umutex.cpp`, in the platform `#ifdef` chains; these are
part of the standard ICU distribution.
**Mutex Header**
```c++
// Example of an ICU build time customized mutex header.
//
// Must define struct UMutex and an initializer that will work with static instances.
// All UMutex instances in ICU code will be static.
#ifndef ICU_MUTEX_C11_H
#define ICU_MUTEX_C11_H
#include <mutex>
#include <condition_variable>
struct UMutex {
std::mutex fMutex;
};
#define U_MUTEX_INITIALIZER {}
#endif
```
**Atomics Header**
```c++
#include <atomic>
typedef std::atomic<int32_t> u_atomic_int32_t;
#define ATOMIC_INT32_T_INITIALIZER(val) ATOMIC_VAR_INIT(val)
inline int32_t umtx_loadAcquire(u_atomic_int32_t &var) {
return var.load(std::memory_order_acquire);
}
inline void umtx_storeRelease(u_atomic_int32_t &var, int32_t val) {
var.store(val, std::memory_order_release);
}
inline int32_t umtx_atomic_inc(u_atomic_int32_t &var) {
return var.fetch_add(1) + 1;
}
inline int32_t umtx_atomic_dec(u_atomic_int32_t &var) {
return var.fetch_sub(1) - 1;
}
```
**Mutex and InitOnce implementations**
```c++
//
// Example ICU build time custom mutex cpp file.
//
// Must implement these functions:
// umtx_lock(UMutex *mutex);
// umtx_unlock(UMutex *mutex);
// umtx_initImplPreInit(UInitOnce &uio);
// umtx_initImplPostInit(UInitOnce &uio);
U_CAPI void U_EXPORT2
umtx_lock(UMutex *mutex) {
if (mutex == NULL) {
// Note: globalMutex is pre-defined in the platform-independent ICU code.
mutex = &globalMutex;
}
mutex->fMutex.lock();
}
U_CAPI void U_EXPORT2
umtx_unlock(UMutex* mutex) `{
if (mutex == NULL) {
mutex = &globalMutex;
}
mutex->fMutex.unlock();
}
// A mutex and a condition variable are used by the implementation of umtx_initOnce()
// The mutex is held only while the state of the InitOnce object is being changed or
// tested. It is not held while initialization functions are running.
// Threads needing to block, waiting for an initialization to complete, will wait
// on the condition variable.
// All InitOnce objects share a common mutex and condition variable. This means that
// all blocked threads will wake if any (possibly unrelated) initialization completes.
// Which does no harm, it should be statistically rare, and any spuriously woken
// threads will check their state and promptly wait again.
static std::mutex initMutex;
static std::condition_variable initCondition;
// This function is called from umtx_initOnce() when an initial test of a UInitOnce::fState flag
// reveals that initialization has not completed, that we either need to call the
// function on this thread, or wait for some other thread to complete the initialization.
//
// The actual call to the init function is made inline by template code
// that knows the C++ types involved. This function returns TRUE if
// the inline code needs to invoke the Init function, or FALSE if the initialization
// has completed on another thread.
//
// UInitOnce::fState values:
// 0: Initialization has not yet begun.
// 1: Initialization is in progress, not yet complete.
// 2: Initialization is complete.
//
UBool umtx_initImplPreInit(UInitOnce &uio) {
std::unique_lock<std::mutex> initLock(initMutex);
int32_t state = uio.fState;
if (state == 0) {
umtx_storeRelease(uio.fState, 1);
return TRUE; // Caller will next call the init function.
} else {
while (uio.fState == 1) {
// Another thread is currently running the initialization.
// Wait until it completes.
initCondition.wait(initLock);
}
U_ASSERT(uio.fState == 2);
return FALSE;
}
}
// This function is called from umtx_initOnce() just after an initializationfunction completes.
// Its purpose is to set the state of the UInitOnce object to initialized, and to
// unblock any threads that may be waiting on the initialization.
//
// Some threads may be waiting on the condition variable, requiring the notify_all().
// Some threads may be racing to test the fState flag outside of the mutex,
// requiring the use of store-release when changing its value.
void umtx_initImplPostInit(UInitOnce &uio) {
std::unique_lock<std::mutex> initLock(initMutex);
umtx_storeRelease(uio.fState, 2);
initCondition.notify_all();
}
```