2

When I started to look into threading in c++11, I thought that .join() was used to make a blocking operation on the main thread and std::async() was used to run non-blocking threads.

This answer explains std:async() pretty well in my opinion. https://stackoverflow.com/a/15035157/1770034

But I wanted to understand the join method better. I found several examples like this: https://stackoverflow.com/a/11229853/1770034 Where only 1 thread is created from the main thread.

Then I found this https://solarianprogrammer.com/2011/12/16/cpp-11-thread-tutorial/

Snippet of Code from site:

#include <iostream>
#include <thread>

static const int num_threads = 10;

//This function will be called from a thread

void call_from_thread(int tid) {
    std::cout << "Launched by thread " << tid << std::endl;
}

int main() {
    std::thread t[num_threads];

    //Launch a group of threads
    for (int i = 0; i < num_threads; ++i) {
        t[i] = std::thread(call_from_thread, i);
    }

    std::cout << "Launched from the main\n";

    //Join the threads with the main thread
    for (int i = 0; i < num_threads; ++i) {
        t[i].join();
    }

    return 0;
}

The part I'm curious about this this part right here:

//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) {
    t[i].join();
}

If the main thread stops to wait until .join() is complete, how can the loop run to join more threads? The fact it works is great! But, why does it work?

Why does it work this way? This was my impression of how it worked.

  1. Main thread joins 1st thread.
  2. Main thread waits until 1st thread finishes.
  3. 1st thread finishes.
  4. Main thread continues in for loop and joins 2nd thread.
  5. Main thread waits until 2nd thread finishes.
  6. ... ... ...

If it keep cycling through the for loop when does the main thread actually get blocked to wait?

Edit

Example:

//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) {
    t[i].join();
    // --- Perform some long operation here ---
}

When would the long operation take place?

Community
  • 1
  • 1
Questionable
  • 768
  • 5
  • 26
  • Your numbered list at the end is perhaps misleading: joining a thread means "waiting for it to finish", i.e. each of the step pairs 2 and 3, 5 and 6 etc. is in fact a single step. The meaning of "join" is not the meaning of the word in "I joined her for a walk", i.e. we walked together. The threads do not run together, but the caller waits until the callee has finished running. It's like joining a marathon runner at the finish line. – Peter - Reinstate Monica Mar 30 '17 at 12:43
  • @Peter So if the caller waits until the callee has finished running. Why doesn't it wait until the first runner in the marathon finishes before dispatching the second runner to start running in the marathon? – Questionable Mar 30 '17 at 13:02
  • 1
    Because if you have a thousand runners in the marathon, the event would take something like 3000 hours. If they all run at the same time it is finished when old uncle Joe finally crosses shortly before dark, on the same day. That's the whole idea of concurrency, quite literally here... In reality you likely have only 2, 4 or 8 lanes (on a dual-, quad- or octa-core), but you are still finished in about 50%, 25% or 12.5% of the time compared to your suggestion. – Peter - Reinstate Monica Mar 30 '17 at 13:18
  • @Peter I've added an edit with an example of what my concern is. – Questionable Mar 30 '17 at 13:24
  • The long operation would take place after the first thread finished; it would be performed while a maximum of 9 threads are still running in parallel. (Although in this example they are probably all finished, just haven't been joined.) Then again after the second thread finished; it would then be performed while at most 8 remaining threads are still running; and so on. – Peter - Reinstate Monica Mar 30 '17 at 13:47
  • @Peter Woah, I think I've been looking at this completely wrong. Does the thread actually start running at `t[i] = std::thread(call_from_thread, i);` and `t[i].join();` means stop and wait for the thread to finish. If it's already finished keep moving forward? And you'd use `std::async()` if you wanted to "check" if it is finished and not say, stop here and wait no matter what? – Questionable Mar 30 '17 at 14:00
  • Yes, C++ threads start immediately upon creation, and `join()` stops and waits for it. I do not know `async()`, so sorry there. Btw, I hate the chat. If I find time I'll make an answer and we can delete this exchange. For now it's ok, nobody except us is reading this anyway. – Peter - Reinstate Monica Mar 30 '17 at 14:28
  • 1
    Well, I'm reading it... – ThingyWotsit Mar 30 '17 at 18:04

1 Answers1

2

When I posted this question I didn't understand exactly what was going on when it came to multi-threading.

Even in reading several tutorials on the subject matter in C++, it can still be a bit hazy what is exactly going on. So this is an explanation of what is actually happening.

To keep it simple let's use this code:

#include <thread>

void my_function()
{
     *** Some Code ***
}

int main()
{
    std::thread threads[10]; //create 10 std::thread objects

    for(int loop = 0; loop < 10; loop++)
    {
        threads[loop] = std::thread(my_function);
    }

    //Threads are executing on machine and main code is also running

    for(int loop = 0; loop < 10; loop++)
    {
        threads[loop].join; //If threads[loop] is still running wait for it, otherwise keep moving forward.
    }

    return 0;
} //all 10 thread objects (threads) are going out of scope and their destructors are being called

At the top of main (in the main thread) we create space for 10 thread objects on the stack. Inside the first for loop we actually create a std::thread, which will call my_function on a separate thread.

Let's assume that we have a quad-core processor and one thread can only run at a processor at a time. (Quite the assumption!) The main thread is currently using one of the processors and one of our 10 threads will use the second one. One of the remaining 9 will use the third one and one of the remaining 8 will use the fourth one.

Between the for loops we can execute all the code we want on our main thread. (ie: the main thread is not blocked). Next we get to the second for loop. This loop will go through and tell each of the threads to join.

Let's pretend that threads 1-9 have executed by this point in time but we are still running thread 10 in the background. When the for loop joins thread 1. Since it is already complete the for loop runs again. This continues to happen through the first 9 threads. Now we join thread 10. It is still running, the main thread will now wait until thread 10 is done. (This is good if you need code done at a certain point before you can move forward.)

Next we have return 0; and the std::threads that we created go out of scope and since they are objects the destructor is called. Since all the threads are joined to the main, no exceptions are thrown and everything ends nicely.

If we thought, "hey, I don't care if the thread finished or not, this program is over." When the std::thread destructor is called the std::thread object will noticed that something is wrong and raise std::terminate.

If we really don't care when the thread finishes or if it finishes. We can use detach. This will allow it to run until it completes its task. If it is still running at the end of main the thread will die without exception (a modern OS will kill it with the process.)

What is joinable? joinable will be true if you did not join a thread to or detach a thread from the caller (the main thread in our case). If you want to check if a thread is still running and not join it until it is. You'll need to use std::async instead. (Can C++11 tell if std::thread is active?)

Community
  • 1
  • 1
Questionable
  • 768
  • 5
  • 26