Professional C__ - Marc Gregoire [387]
Result = 84157
Result = 77133
Result = 74325
This code is clearly showing race condition behavior. In this example, the best and recommended solution is to use the new atomic types. The following code highlights the required changes:
#include #include #include #include #include void func(std::atomic { for (int i = 0; i < 10000; ++i) { ++counter; } } int main() { std::atomic std::vector 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_atomic.cpp The changes add the Result = 100000 Result = 100000 Result = 100000 Result = 100000 Result = 100000 Without explicitly adding any locks to your code, you have made it thread-safe and race condition free because the ++counter operation on an atomic type will load the value, increment the value, and store the value in one atomic transaction, which cannot be interrupted. If you want to compile multithreaded code with GCC, you have to link with the pthread library. For example: > gcc -lstdc++ -lpthread inc_func_atomic.cpp Atomic Operations The C++11 standard defines a number of atomic operations. This section describes a few of those operations. For a full list, consult the Standard Library Reference resource on the website, www.wrox.com. An example of an atomic operation is: bool atomic_compare_exchange_strong(atomic This operation can also be called as a member of atomic bool atomic This operation implements the following logic atomically: if (memcmp(object, expected, sizeof(*object)) == 0) { memcpy(object, &desired, sizeof(*object)); return true; } else { memcpy(expected, object, sizeof(*object)); return false; } A second example is atomic atomic cout << "Value = " << value << endl; int fetched = value.fetch_add(4); cout << "Fetched = " << fetched << endl; cout << "Value = " << value << endl; Code snippet from atomic\fetch_add.cpp If no other threads are touching the contents of the fetched and value variables, the output is as follows: Value = 10 Fetched = 10 Value = 14 Atomic integral types support the following atomic operations: fetch_add(), fetch_sub(), fetch_and(), fetch_or(), fetch_xor(), ++, --, +=, -=, &=, ^= and |=. Atomic pointer types support fetch_add(), fetch_sub(), ++, --, += and -=. Most of the atomic operations can accept an extra parameter specifying the memory ordering that you would like. For example: T atomic You may change the default memory_order. The standard provides: memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel and memory_order_seq_cst, all defined in the std namespace. However, it is rare that you will want to use them instead of the default. While another memory order may perform better than the default, according to some metric, if you use them slightly wrong you will again introduce race conditions or other difficult-to-track threading-related problems. If you do need to know more about memory ordering, consult one of the multithreading references in Appendix B. THREADS
The C++11 threading library, defined in the