Professional C__ - Marc Gregoire [389]
Counter Counter 2 has value 0
1 has value 0
Counter 1 has value 1
Counter 1 has value 2
Instead of:
Counter 1 has value 0
Counter 2 has value 0
Counter 1 has value 1
Counter 1 has value 2
This can be fixed by using synchronization methods, which are discussed later in this chapter.
Thread function arguments are always copied into some internal storage for the thread. Use std::ref() from the Thread with Function Object The previous section demonstrated how to create a thread and tell it to run a specific function in the new thread by passing a pointer to the function to execute. You can also use a function object, as shown in the following example. With the function pointer technique discussed earlier, the only way to pass information to the thread is by passing arguments to the function. With function objects you can add member variables to your function object class, which you can initialize and use however you want. The example first defines a class called Counter. It has two member variables: an ID and the number of iterations for the loop. Both of these member variables are initialized with the class constructor. To make the Counter class a function object, you need to implement operator(), as discussed in Chapter 13. The implementation of operator() is the same as the counter() function in the previous section: class Counter { public: Counter(int id, int numIterations) : mId(id), mNumIterations(numIterations) { } void operator()() const { for (int i = 0; i < mNumIterations; ++i) { cout << "Counter " << mId << " has value "; cout << i << endl; } } protected: int mId; int mNumIterations; }; Code snippet from thread\ThreadFunctionObject.cpp Three methods for initializing threads with a function object are demonstrated in the following main(). The first uses the C++11 uniform initialization syntax. You create an instance of the Counter class with its constructor arguments and give it to the thread constructor between curly braces. The second defines a named instance of the Counter class and gives this named instance to the constructor of the thread class. The third looks similar to the first; it creates an instance of the Counter class and gives it to the constructor of the thread class, but uses parentheses instead of curly braces. The ramifications of this are discussed after the code. int main() { cout.sync_with_stdio(true); // Make sure cout is thread-safe // Using C++11 initialization syntax thread t1{Counter(1, 20)}; // Using named variable Counter c(2, 12); thread t2(c); // Using temporary thread t3(Counter(3, 10)); // Wait for threads to finish t1.join(); t2.join(); t3.join(); return 0; } Code snippet from thread\ThreadFunctionObject.cpp If you compare the creation of t1 with the creation of t3, it looks like the only difference seems to be that the first method uses curly braces while the third method uses brackets. However, when your function object constructor doesn’t require any parameters, the third method as written above will not work. For example: class Counter { public: Counter() {} void operator()() const { /* Omitted for brevity */ } }; int main() { cout.sync_with_stdio(true); // Make sure cout is thread-safe thread t1(Counter()); // Error! t1.join(); return 0; } This will result in a compilation error because C++ will interpret the second line in the main() function as a declaration of a function called t1, which returns a thread object and accepts a pointer to a function without parameters returning a Counter object. For this reason, it’s recommended to use the C++11 uniform initialization syntax: thread t1{Counter()}; // OK If your compiler does not support this new syntax you have to add an extra set of parentheses to prevent the compiler from interpreting the line as a function declaration: thread t1((Counter())); // OK Function objects are always copied into some internal storage for the thread. If you want