Online Book Reader

Home Category

Professional C__ - Marc Gregoire [386]

By Root 1362 0
a certain time interval, the thread stops waiting and possibly releases other locks it is currently holding. The thread might then sleep for a little bit and try again later to acquire all the resources it needs. This method might give other threads the opportunity to acquire necessary locks and continue their execution. Whether this method works or not depends heavily on your specific deadlock case.

Instead of using a workaround as described in the previous paragraph, you should try to avoid any possible deadlock situation altogether. If you need to acquire multiple locks, the recommended way is to use the standard std::lock() or std::try_lock() methods described later in the section on mutual exclusion. These methods will obtain or try to obtain a lock on several resources, doing their best to prevent deadlocks.

ATOMIC OPERATIONS LIBRARY


C++11 introduces atomic types on which atomic operations can be applied. These allow atomic accesses, which means that concurrent reading and writing without additional synchronization is allowed. Reads will never result in undefined behavior.

The increment race condition example given in the previous section can be solved by using an atomic type. For example, the following code is not thread-safe and can show race condition behavior as explained earlier:

int counter = 0; // Global variable

...

++counter; // Executed in multiple threads

To make this thread-safe without explicitly using any locks, use an atomic type:

atomic counter(0) ; // Global variable

...

++counter; // Executed in multiple threads

You need to include the header to use these atomic types. The standard defines the following named integral atomic types:

NAMED ATOMIC TYPE EQUIVALENT ATOMIC TYPE INTEGRAL TYPE

atomic_char atomic char

atomic_schar atomic signed char

atomic_uchar atomic unsigned char

atomic_short atomic short

atomic_ushort atomic unsigned short

atomic_int atomic int

atomic_uint atomic unsigned int

atomic_long atomic long

atomic_ulong atomic unsigned long

atomic_llong atomic long long

atomic_ullong atomic unsigned long long

atomic_char16_t atomic char16_t

atomic_char32_t atomic char32_t

atomic_wchar_t atomic wchar_t

Atomic Type Example

This section explains why you should use atomic types. Suppose you have a function called func() that increments an integer given as a reference parameter in a loop:

void func(int& counter)

{

for (int i = 0; i < 10000; ++i) {

++counter;

}

}

Code snippet from atomic\inc_func_non_atomic.cpp

Now, you would like to run several threads in parallel, all executing this func() function. By implementing this naively without atomic types, you introduce a race condition. The following main() function launches several threads with the std::thread class defined in the header file. The constructor of the thread class requires a pointer to a function to execute in the new thread, and the arguments for the function. After having launched 10 threads, the main() function waits for all threads to finish, by calling join() on each thread. The details of how to use std::thread and join() are not important at this point and are discussed later in this chapter.

#include

#include

#include

#include

int main()

{

int counter = 0;

std::vector threads;

for (int i = 0; i < 10; ++i) {

threads.push_back(std::thread{func, std::ref(counter)});

}

for (auto& t : threads) {

t.join();

}

std::cout << "Result = " << counter << std::endl;

return 0;

}

Code snippet from atomic\inc_func_non_atomic.cpp

Since func() increments the integer 10.000 times, and main() launches 10 background threads, each of which executes func(), the expected result is 100.000. If you execute this program several times, you might get the following output:

Result = 86044

Result = 100000

Result = 99726

Result = 90780

Result = 100000

Result = 85054

Return Main Page Previous Page Next Page

®Online Book Reader