BDE 4.14.0 Production release
|
Macros | |
#define | BSLMT_ONCE_UNIQNUM __LINE__ |
#define | BSLMT_ONCE_DO BSLMT_ONCE_DO_IMP(BSLMT_ONCE_CAT(bslmt_doOnceObj, BSLMT_ONCE_UNIQNUM)) |
#define | BSLMT_ONCE_CANCEL() bslmt_doOnceGuard.cancel() |
#define | BSLMT_ONCE_INITIALIZER { BSLMT_QLOCK_INITIALIZER, { 0 } } |
#define | BSLMT_ONCE__CAT(X, Y) BSLMT_ONCE_CAT(X, Y) |
Provide a thread-safe way to execute code once per process.
bslmt::Once
This component provides a pair of classes, bslmt::Once
and bslmt::OnceGuard
, which give the caller a way to run a body of code exactly once within the current process, particularly in the presence of multiple threads. This component also defines the macro BSLMT_ONCE_DO
, which provides syntactic sugar to make one-time execution nearly fool-proof. A common use of one-time execution is the initialization of singletons on first use.
The bslmt::Once
class is designed to be statically allocated and initialized using the BSLMT_ONCE_INITIALIZER
macro. Client code may use the bslmt::Once
object in one of two ways: 1) it may use the callOnce
method to call a function or functor or 2) it may call the enter
and leave
methods just before and after the code that is intended to be executed only once. That code must be executed conditionally on enter
returning true
, indicating that the caller is the first thread to pass through this region of code. The leave
method must be executed at the end of the code region, indicating that the one-time execution has completed and unblocking any threads waiting on enter
.
A safer way to use the enter
and leave
methods of bslmt::Once
is to manage the bslmt::Once
object using a bslmt::OnceGuard
object constructed from the bslmt::Once
object. Calling enter
on the bslmt::OnceGuard
object will call enter
on its associated bslmt::Once
object. If the call to enter
returns true
, then the destructor for the guard will automatically call leave
on its associated bslmt::Once
object. The bslmt::OnceGuard
class is intended to be allocated on the stack (i.e., as a local variable) so that it is automatically destroyed at the end of its enclosing block. Thus, the to call leave
of the bslmt::Once
object is enforced by the compiler.
An even easier way to use the facilities of this component is to use the BSLMT_ONCE_DO
macro. This macro behaves like an if
statement – executing the following [compound] statement the first time the control passes through it in the course of a program's execution, and blocking other calling threads until the [compound] statement is executed the first time. Thus, bracketing arbitrary code in a BSLMT_ONCE_DO
construct is the easiest way to ensure that code will be executed only once for a program. The BSLMT_ONCE_DO
behaves correctly even if there are return
statements within the one-time code block.
The implementation of this component uses appropriate memory barriers so that changes made in the one-time execution code are immediately visible to all threads at the end of the one-time code block.
The BSLMT_ONCE_DO
macro consists of a declaration and a for
loop. Consequently, the following is syntactically incorrect:
Also, a break
or continue
statement within a BSLMT_ONCE_DO
construct terminates the BSLMT_ONCE_DO
, not a surrounding loop or switch
statement. For example:
Objects of the bslmt::Once
class are intended to be shared among threads and may be accessed and modified simultaneously in multiple threads by using the methods provided. To allow static initialization, bslmt::Once
is a POD type with public member variables. It is not safe to directly access or manipulate its member variables (including object initialization) simultaneously from multiple threads. (Note that static initialization takes place before multiple threading begins, and is thus safe.)
The bslmt::OnceGuard
objects are designed to be used only by their creator threads and are typically created on the stack. It is not safe to use a bslmt::OnceGuard
by a thread other than its creator.
Typically, the facilities in this component are used to implement a thread-safe singleton. Below, we implement the a singleton four ways, illustrating the two ways to directly use bslmt::Once
, the use of bslmt::OnceGuard
, and the use of BSLMT_ONCE_DO
. In each example, the singleton functions take a C-string (const char*
) argument and return a reference to a bsl::string
object constructed from the input string. Only the first call to each singleton function affect the contents of the singleton string. (The argument is ignored on subsequent calls.)
Our first implementation uses the BSLMT_ONCE_DO
construct, the recommended way to use this component. The function is a variation of the singleton pattern described by Scott Meyers, except that the BSLMT_ONCE_DO
macro is used to handle multiple entries to the function in a thread-safe manner:
The BSLMT_ONCE_DO
mechanism suffices for most situations; however, if more flexibility is required, review the remaining examples in this series for more design choices. The next example will use the lowest-level facilities of bslmt::Once
. The two following examples use progressively higher-level facilities to produce simpler singleton implementations (though none as simple as the BSLMT_ONCE_DO
example above).
The next singleton function implementation directly uses the doOnce
method of bslmt::Once
. We begin by declaring a functor type that does most of the work of the singleton, i.e., constructing the string and setting a (static) pointer to the string:
The function call operator of the type above is not thread-safe. Firstly, many threads might attempt to simultaneously construct the theSingleton
object. Secondly, once theSingletonPtr
is set by one thread, other threads still might not see the change (and try to initialize the singleton again).
The singleton1
function, below, invokes SingletonInitializer::operator()
via the callOnce
method of bslmt::Once
to ensure that operator()
is called by only one thread and that the result is visible to all threads. We start by creating and initializing a static object of type bslmt::Once
:
We construct a SingletonInitializer
instance, effectively "binding" the argument s
so that it may be used in a function invoked by callOnce
, which takes only a no-argument functor (or function). The first thread (and only the first thread) entering this section of code will set theSingleton
.
Once we return from callOnce
, the appropriate memory barrier has been executed so that the change to theSingletonPtr
is visible to all threads. A thread calling callOnce
after the initialization has completed would immediately return from the call. A thread calling callOnce
while initialization is still in progress would block until initialization completes and then return.
Implementation Note: As an optimization, developers sometimes pre-check the value to be set, theSingletonPtr
in this case, to avoid (heavy) memory barrier operations; however, that practice is not recommended here. First, the value of the string may be cached by a different CPU, even though the pointer has already been updated on the common memory bus. Second, The implementation of the callOnce
method is fast enough that a pre-check would not provide any performance benefit.
The one advantage of this implementation over the previous one is that an exception thrown from within singletonImp
will cause the bslmt::Once
object to be restored to its original state, so that the next entry into the singleton will retry the operation.
Our next implementation, singleton2
, eliminates the need for the singletonImp
function and thereby does away with the use of the a functor type to "bind" the initialization parameter; however, it does require use of bslmt::Once::OnceLock
, created on each thread's stack and passed to the methods of bslmt::Once
. First, we declare a static bslmt::Once
object as before, and also declare a static pointer to bsl::string
:
Next, we define a local bslmt::Once::OnceLock
object and pass it to the enter
method:
If the enter
method returns true
, we proceed with the initialization of the singleton, as before.
When initialization is complete, the leave
method is called for the same context cookie previously used in the call to enter
:
When any thread reaches this point, initialization has been complete and initialized string is returned:
Our final implementation, singleton3
, uses bslmt::OnceGuard
to simplify the previous implementation by using bslmt::OnceGuard
to hide (automate) the use of bslmt::Once::OnceLock
. We begin as before, defining a static bslmt::Once
object and a static bsl::string
pointer:
We then declare a local bslmt::OnceGuard
object and associate it with the bslmt::Once
object before entering the one-time initialization region:
Note that it is unnecessary to call onceGuard.leave()
because that is called automatically before the function returns. This machinery makes the code more robust in the presence of, e.g., return statements in the initialization code.
If there is significant code after the end of the one-time initialization, the guard and the initialization code should be enclosed in an extra block so that the guard is destroyed as soon as validly possible and allow other threads waiting on the initialization to continue. Alternatively, one can call onceGuard.leave()
explicitly at the end of the initialization.
The following pair of functions, thread1func
and thread2func
which will be run by different threads:
Both threads attempt to initialize the four singletons. In our example, each thread passes a distinct argument to the singleton, allowing us to identify the thread that initializes the singleton. (In practice, the arguments passed to a specific singleton are almost always fixed and most singletons don't take arguments at all.)
Assuming that the first thread function wins all of the races to initialize the singletons, the first singleton is set to "0 hello", the second singleton to "1 hello", etc.
#define BSLMT_ONCE__CAT | ( | X, | |
Y | |||
) | BSLMT_ONCE_CAT(X, Y) |
#define BSLMT_ONCE_CANCEL | ( | ) | bslmt_doOnceGuard.cancel() |
This macro provides a way to cancel once processing within a BSLMT_ONCE_DO
construct. It will not compile outside of a BSLMT_ONCE_DO
construct. Executing this function-like macro will set the state of the BSLMT_ONCE_DO
construct to "not entered", possibly unblocking a thread waiting to enter the one-time code region. Note that this macro does not exit the BSLMT_ONCE_DO
construct (i.e., it does not have break
or return
semantics).
#define BSLMT_ONCE_DO BSLMT_ONCE_DO_IMP(BSLMT_ONCE_CAT(bslmt_doOnceObj, BSLMT_ONCE_UNIQNUM)) |
This macro provides a simple control construct to bracket a piece of code that should only be executed once during the course of a multithreaded program. Usage:
Leaving a BSLMT_ONCE_DO
construct via break
, continue
, or return
will put the construct in a "done" state (unless BSLMT_ONCE_CANCEL
has been called) and will unblock all threads waiting to enter the one-time region. Note that a break
or continue
within the one-time code will terminate only the BSLMT_ONCE_DO
construct, not any surrounding loop or switch statement. Due to a bug in the Microsoft Visual C++ 2003 compiler, the behavior is undefined if an exception is thrown from within this construct and is not caught within the same construct. Only one call to BSLMT_ONCE_DO
may appear on a single source-code line in any code block.
#define BSLMT_ONCE_INITIALIZER { BSLMT_QLOCK_INITIALIZER, { 0 } } |
#define BSLMT_ONCE_UNIQNUM __LINE__ |