C++11/C++14 6. Thread with Move Semantics - 2020
"If you're an experienced C++ programmer and are anything like me, you initially
approached C++11 thinking, "Yes, yes, I get it. It's C++, only more so." But as you
learned more, you were surprised by the scope of the changes. auto declarations,
range-based for loops, lambda expressions, and rvalue references change the face of
C++, to say nothing of the new concurrency features. And then there are the
idiomatic changes. 0 and typedefs are out, nullptr and alias declarations are in.
Enums should now be scoped. Smart pointers are now preferable to built-in ones.
Moving objects is normally better than copying them.
- Effective Modern C++ by Scott Meyers
What's the implication of passing a variable by value?
When a function gets its parameter as by value, it does copy. Compiler knows how to copy it. If it's a user defined type, we may need to provide copy constructor, and may be assignment operator as well to be able to do a deep copy. However, copying is expensive.
Actually, there are bunch of copying igoing on when use STL containers. When an object tossed in by value, and it's a temporary object (rvalue), then, we're really wasting our precious resources including CPU as well as memory and so on.
So, we need to find a way to avoid the unnecessary copy.
The move semantics is one way of doing it.
We'll start from this code:
#include <iostream> #include <thread> #include <vector> #include <algorithm> #include <cassert> int main() { // vector container stores threads std::vector<std::thread> workers; for (int i = 0; i < 5; i++) { auto t = std::thread([i]() { std::cout << "thread function: " << i << "\n"; }); workers.push_back(std::move(t)); } std::cout << "main thread\n"; // Looping every thread via for_each // The 3rd argument assigns a task // It tells the compiler we're using lambda ([]) // The lambda function takes its argument as a reference to a thread, t // Then, joins one by one, and this works like barrier std::for_each(workers.begin(), workers.end(), [](std::thread &t;) { assert(t.joinable()); t.join(); }); return 0; }
We'll make a little change to make it more general. Let's separate the thread function, task(), from the lambda, and stop using lambda's capturing the index i:
#include <iostream> #include <thread> #include <vector> #include <algorithm> #include <cassert> void task(int i) { std::cout << "worker : " << i << "\n"; } int main() { // vector container stores threads std::vector<std::thread> workers; for (int i = 0; i < 5; i++) { auto t = std::thread(&task;, i); workers.push_back(std::move(t)); } std::cout << "main thread\n"; // Looping every thread via for_each // The 3rd argument assigns a task // It tells the compiler we're using lambda ([]) // The lambda function takes its argument as a reference to a thread, t // Then, joins one by one, and this works like barrier std::for_each(workers.begin(), workers.end(), [](std::thread &t;) { assert(t.joinable()); t.join(); }); return 0; }
Output from the run:
worker : 0 worker : 1 worker : 2 main thread worker : 3worker : 4
At this point, we just want to check if the code is still working.
Let's pass the thread argument by reference like this code:
void task(int &i) { std::cout << "worker : " << i << "\n"; }
We also need to change the argument of thread constructor:
auto t = std::thread(&task;, std::ref(i));
It seems to be working fine, but in general, tossing reference (aliases) around is not a good idea. Because it means multiple threads are holding a reference which is pointing to the same object.
C++11/C++14 Thread Tutorials
C++11 1. Creating Threads
C++11 2. Debugging with Visual Studio 2013
C++11 3. Threading with Lambda Function
C++11 4. Rvalue and Lvalue
C++11 5. Move semantics and Rvalue Reference
C++11 5B. Move semantics - more samples for move constructor
C++11 6. Thread with Move Semantics and Rvalue Reference
C++11 7. Threads - Sharing Memory and Mutex
C++11 8. Threads - Race Conditions
C++11 9. Threads - Deadlock
C++11 10. Threads - Condition Variables
C++11 11. Threads - unique futures (std::future<>) and shared futures (std::shared_future<>).
C++11 12. Threads - std::promise
C++11/C++14 New Features
initializer_list
Uniform initialization
Type Inference (auto) and Range-based for loop
The nullptr and strongly typed enumerations
Static assertions and Constructor delegation
override and final
default and delete specifier
constexpr and string literals
Lambda functions and expressions
std::array container
Rvalue and Lvalue (from C++11 Thread tutorial)
Move semantics and Rvalue Reference (from C++11 Thread tutorial)
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization