5

The following code is from Dash's example for std::thread.

#include <iostream>
#include <thread>
#include <chrono>

void foo()
{
    // simulate expensive operation
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

void bar()
{
    // simulate expensive operation
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

int main()
{
    std::cout << "starting first helper...\n";
    std::thread helper1(foo);

    std::cout << "starting second helper...\n";
    std::thread helper2(bar);

    std::cout << "waiting for helpers to finish..." << std::endl;
    helper1.join();
    // std::cout << "after join... \n";
    helper2.join();

    std::cout << "done!\n";
}

join blocks the current thread until the thread identified by *this finishes its execution.

Does thread execute after join called?

If I add std::cout << "after join... \n" after the first join, the after join... and done! will output sequentially, without delay, which just like being put after second join.

To be specific, the whole effect is : print the first three lines sequentially without delay, then sleep for some while, final print the last two lines sequentially without delay.

a.join();
b.join();
cout << "something...";
// or
a.join();
cout << "something...";
b.join();

What confused me is : Why do the two ways have same effect? What does join do exactly?

chenzhongpu
  • 6,193
  • 8
  • 41
  • 79

4 Answers4

8

Both of your threads start at the same time and execute for the same length. Thus the join in your case does not do much - both threads start at the same time and end at the same time.

  1. Thread 1 starts, starts sleeping for 1 second
  2. Thread 2 starts, starts sleeping for 1 second
  3. Join on 1 called - program will wait for 1 second to pass for Thread 1 to finish
  4. This means that thread 2 finishes as well
  5. Join on 2 called, this is simply passthrough as Thread 2 is already finished

As you can see printing anything before or after step 5 changes nothing.

A better example would be this:

#include <iostream>
#include <thread>
#include <chrono>

using namespace std;

void foo()
{
    // simulate expensive operation
    cout << "Foo Start" << endl;
    this_thread::sleep_for(chrono::seconds(1));
    cout << "Foo Stop" << endl;
}

void bar()
{
    // simulate expensive operation
    cout << "Bar Start" << endl;
    this_thread::sleep_for(chrono::seconds(2));
    cout << "Bar Stop" << endl;
}

int main()
{
    thread helper1(foo);
    thread helper2(bar);

    helper1.join();
    cout << "Joined Foo... \n";
    helper2.join();
    cout << "Joined Bar... \n";
}

You will observe that if thread foo lasts for 1 second and thread bar for 2 seconds, then the "Joined Foo" and "Joined Bar" outputs will come with a 1 second delay in between. If you invert the durations to 2 seconds for foo and 1 second for bar, then the "Joined Foo" output will print after 2 seconds and "Joined Bar" immediately after that.

Jozef Legény
  • 1,157
  • 1
  • 11
  • 26
2

In one sentence, the joiner waits for the joinee to finish its execution.

David Haim
  • 25,446
  • 3
  • 44
  • 78
1

Join blocks the current thread, meaning it will not continue running, until the thread the join is called upon will finish. The secondary threads start running on the constructor call. E.g. the main thread will stop its execution until thread a finishes its running, then the main thread will resume

Efi Weiss
  • 638
  • 6
  • 12
0

I'll give an example to illustrate what join does and can be used for. Let's say I have a large batch of calculations B to do, and I want to do them in parallel, using multiple threads, to utilize the multiple CPU cores I have.

Let's call the thread I'm starting all this from main. Let's say I have four cores, so I'll use four threads. From the thread main I create four new 'worker' threads, and give them all a subset of B to process.

I now have 5 running threads. But in my main thread, I want to use the results of the calculations, so I have to wait for the other threads to finish. That is done with join, by calling join one by one on the four threads, I make sure that they all finish calculating, so I can safely read and use the results.

By calling join from main on one of the four worker threads, I'm making main wait on that worker thread. So main will halt while it's waiting, it is a 'blocking call'.

Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93