Questionable
Questionable

Reputation: 878

Does the main thread run on blocking threads?

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?

Upvotes: 2

Views: 2023

Answers (1)

Questionable
Questionable

Reputation: 878

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?)

Upvotes: 2

Related Questions