BDE 4.14.0 Production release
Loading...
Searching...
No Matches

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)
 

Detailed Description

Outline

Purpose

Provide a thread-safe way to execute code once per process.

Classes

See also
bslmt_qlock

Description

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.

Warning

The BSLMT_ONCE_DO macro consists of a declaration and a for loop. Consequently, the following is syntactically incorrect:

if (xyz) BSLMT_ONCE_DO { stuff() }
#define BSLMT_ONCE_DO
Definition bslmt_once.h:425

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:

switch (xyz) {
case 0: BSLMT_ONCE_DO { stuff(); break; /* does not break case */ }
case 1: // Oops! case 0 falls through to here.
}

Thread Safety

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.

Usage

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.)

First Implementation

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:

const bsl::string& singleton0(const char *s)
{
static bsl::string *theSingletonPtr = 0;
static bsl::string theSingleton(s,
theSingletonPtr = &theSingleton;
}
return *theSingletonPtr;
}
Definition bslstl_string.h:1281
static Allocator * globalAllocator(Allocator *basicAllocator=0)
Definition bslma_default.h:905

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).

Second Implementation

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:

static bsl::string *theSingletonPtr = 0;
class SingletonInitializer {
const char *d_initialValue;
public:
SingletonInitializer(const char *initialValue)
: d_initialValue(initialValue)
{
}
void operator()() const {
static bsl::string theSingleton(d_initialValue);
theSingletonPtr = &theSingleton;
}
};

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:

const bsl::string& singleton1(const char *s)
{
Definition bslmt_once.h:456
#define BSLMT_ONCE_INITIALIZER
Definition bslmt_once.h:437

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.callOnce(SingletonInitializer(s));
return *theSingletonPtr;
}
void callOnce(FUNC &function)
Definition bslmt_once.h:676

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.

Third Implementation

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:

const bsl::string& singleton2(const char *s)
{
static bsl::string *theSingletonPtr = 0;

Next, we define a local bslmt::Once::OnceLock object and pass it to the enter method:

if (once.enter(&onceLock)) {
bool enter(OnceLock *onceLock)
Definition bslmt_qlock.h:381

If the enter method returns true, we proceed with the initialization of the singleton, as before.

static bsl::string theSingleton(s);
theSingletonPtr = &theSingleton;

When initialization is complete, the leave method is called for the same context cookie previously used in the call to enter:

once.leave(&onceLock);
}
void leave(OnceLock *onceLock)

When any thread reaches this point, initialization has been complete and initialized string is returned:

return *theSingletonPtr;
}

Fourth Implementation

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:

const bsl::string& singleton3(const char *s)
{
static bsl::string *theSingletonPtr = 0;

We then declare a local bslmt::OnceGuard object and associate it with the bslmt::Once object before entering the one-time initialization region:

bslmt::OnceGuard onceGuard(&once);
if (onceGuard.enter()) {
static bsl::string theSingleton(s);
theSingletonPtr = &theSingleton;
}
return *theSingletonPtr;
}
Definition bslmt_once.h:548

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.

Using the Semaphore Implementations

The following pair of functions, thread1func and thread2func which will be run by different threads:

void *thread1func(void *)
{
const bsl::string& s0 = singleton0("0 hello");
const bsl::string& s1 = singleton1("1 hello");
const bsl::string& s2 = singleton2("2 hello");
const bsl::string& s3 = singleton3("3 hello");
assert('0' == s0[0]);
assert('1' == s1[0]);
assert('2' == s2[0]);
assert('3' == s3[0]);
// ... lots more code goes here
return 0;
}
void *thread2func(void *)
{
const bsl::string& s0 = singleton0("0 goodbye");
const bsl::string& s1 = singleton1("1 goodbye");
const bsl::string& s2 = singleton2("2 goodbye");
const bsl::string& s3 = singleton3("3 goodbye");
assert('0' == s0[0]);
assert('1' == s1[0]);
assert('2' == s2[0]);
assert('3' == s3[0]);
// ... lots more code goes here
return 0;
}

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.

int usageExample1()
{
void startThread1();
void startThread2();
startThread1();
startThread2();
assert(singleton0("0") == "0 hello");
assert(singleton1("1") == "1 hello");
assert(singleton2("2") == "2 hello");
assert(singleton3("3") == "3 hello");
return 0;
}

Macro Definition Documentation

◆ BSLMT_ONCE__CAT

#define BSLMT_ONCE__CAT (   X,
 
)    BSLMT_ONCE_CAT(X, Y)

◆ BSLMT_ONCE_CANCEL

#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).

◆ BSLMT_ONCE_DO

#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:

BSLMT_ONCE_DO { /* one-time code goes here */ }

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.

◆ BSLMT_ONCE_INITIALIZER

#define BSLMT_ONCE_INITIALIZER   { BSLMT_QLOCK_INITIALIZER, { 0 } }

◆ BSLMT_ONCE_UNIQNUM

#define BSLMT_ONCE_UNIQNUM   __LINE__