Multi-threading allows you to execute portions of your code concurrently. While on a single CPU the benefits of multi-threading are limited, multi-core systems allow true concurent execution, thus reducing the time it takes for the task to finish.
The basic threading is easy, you just declare a function and run it in thread.
#include <thread> #include <iostream> using namespace std; void complicatedCalculation1() {//Do stuff here} void complicatedCalculation2() {//Do stuff here} int main() { //Creates a thread and starts it thread calc1Thread(complicatedCalculation1); //Creates a thread and starts it thread calc2Thread(complicatedCalculation1); //Wait for each thread to finish calc1Thread.join(); calc2Thread.join(); cout<<"All done"<<endl; return 0; }
Obviously this example is not very useful – in real life you want to pass parameters in and get results out. Luckily both STL and Boost are heavily templated so you can easily pass up to 9 parameters.
void complicatedCalculation1(int param1, int param2, int& result) {//Do stuff here} void complicatedCalculation2(int param1, int param2, int& result) {//Do stuff here} int main() { int val1, val2, result1, result2; //Creates a thread and starts it thread calc1Thread(complicatedCalculation1, val1, val2, result1); //Creates a thread and starts it thread calc2Thread(complicatedCalculation1, val1, val2, result2); //Wait for each thread to finish calc1Thread.join(); calc2Thread.join(); cout<<"All done. Results: "result1<<" "<<result2<<endl; return 0; }
Here is the tricky part – the code above will calculate the correct results, but won’t print them. See, while you might expect that result will be passed by reference as specified by the function’s prototype, that’s not the case – all parameters are first copied and then passed to your function. To pass something by reference, you need to use the std::ref or std::cref functions (or their boost equivalents):
thread calc1Thread(complicatedCalculation1, val1, val2, ref(result1)); //Creates a thread and starts it thread calc2Thread(complicatedCalculation1, val1, val2, ref(result2));
Another way to work around this is to pass pointers – while the pointer is still copied, the pointee remains the same, thus the changes persist after the end of the thread.
You wonder why the parameters being copied first? I do too, so I dug into the boost documentation. Here is how boost defines the thread constructor:
template <class F,class A1,class A2,...> thread(F f,A1 a1,A2 a2,...); //Which ends up being: thread(boost::bind(f, a1, a2 ...);
The arguments that bind takes are copied and held internally by the returned function object.
It seems to me that the logical thing to do here would’ve been to pass the parameters by reference and let the function prototype figure out what to do with them. I would venture a guess that this was done to ensure that the objects that are passed into the thread are not deallocated while the thread is still running.
I’m still of the opinion that if the prototype of the function expects a reference, the programmer clearly demonstrates intention to modify the value and thus would ensure that the object is not deallocated.
I tend to try what I think to be the most logical thing before I go searching through the documentation and thus this “hidden” copying has really tripped me up in the past.