Professional C__ - Marc Gregoire [395]
class Data
{
public:
void operator()()
{
call_once(mOnceFlag, &Data::init, this);
// Do work
cout << "Work" << endl;
}
protected:
void init()
{
cout << "init()" << endl;
mMemory = new char[24];
}
mutable once_flag mOnceFlag;
mutable char* mMemory;
};
int main()
{
cout.sync_with_stdio(true); // Make sure cout is thread-safe
Data d;
thread t1{ref(d)};
thread t2{ref(d)};
thread t3{ref(d)};
t1.join();
t2.join();
t3.join();
return 0;
}
Code snippet from mutex\call_once.cpp
The output of this code is as follows:
init()
Work
Work
Work
The example omits the code for deallocation of the memory. Of course, in this example, you could also allocate the memory for mMemory in the constructor of the Data class. It is done using the init() method to demonstrate the use of call_once().
Mutex Usage Examples
Thread-Safe Writing to Streams
Earlier in this chapter, in the section about threads, you saw an example with a class called Counter. That example mentioned that C++ streams are thread-safe when you call sync_with_stdio(true), but the output from different threads can still be interleaved. To solve this issue, you can use a mutual exclusion object to make sure that only one thread at a time is reading/writing to the stream object.
The following example synchronizes all accesses to cout in the Counter class. For this, a static mutex object is added to the class. It should be static, because all instances of the class should use the same mutex instance. Before writing to cout, the updated example uses a lock_guard to obtain a lock on the mutex. Changes compared to the earlier version are highlighted:
class Counter
{
public:
Counter(int id, int numIterations)
: mId(id), mNumIterations(numIterations)
{
}
void operator()() const
{
for (int i = 0; i < mNumIterations; ++i) {
lock_guard cout << "Counter " << mId << " has value "; cout << i << endl; } } protected: int mId; int mNumIterations; static mutex mMutex; }; mutex Counter::mMutex; int main() { // Omitted for brevity. } Code snippet from mutex\ThreadFunctionObjectWithMutex.cpp Using Timed Locks The following example demonstrates how to use a timed mutex. It is the same Counter class as before, but this time it uses a timed_mutex in combination with a unique_lock. A relative time of 200 milliseconds is given to the unique_lock constructor, causing it to try to obtain a lock for 200 milliseconds. If the lock could not be obtained within this timeout interval, the constructor returns. Afterwards you can check whether or not the lock has been acquired, which can be done with an if statement on the lock variable because the unique_lock class defines a bool() conversion operator. The timeout is specified by using the C++11 Chrono library, which is discussed in Chapter 16. class Counter { public: Counter(int id, int numIterations) : mId(id), mNumIterations(numIterations) { } void operator()() const { for (int i = 0; i < mNumIterations; ++i) { unique_lock chrono::milliseconds(200)); if (lock) { cout << "Counter " << mId << " has value "; cout << i << endl; } else { // Lock not acquired in 200 ms } } } protected: int mId; int mNumIterations; static timed_mutex mTimedMutex; }; timed_mutex Counter::mTimedMutex; int main() { // Omitted for brevity. } Code snippet from mutex\ThreadFunctionObjectWithTimedMutex.cpp Double-Checked Locking You can use locks to implement the double-checked locking algorithm. This can, for example, be used to make sure that a variable