1

Please help me with a simple question about the output of the following code.

I thought that a thread is executed only when the join() or detach() function is called.

So, I expect the output to print only "pause of 2 seconds ended , ID = 59306", and not to print "pause of 1 seconds ended , ID = 10218" because I thought that only thread 2 would be executed, and thread 1 would NOT be executed. But, I was wrong.

In reality, the output actually prints both of the lines mentioned above, which means both thread 1 and 2 are executed. Is it true ?

Would you please explain to me how the code actually execute both threads ?

==========================

#include <iostream>       // std::cout
#include <thread>         // std::thread, std::this_thread::sleep_for
#include <chrono>         // std::chrono::seconds
 
void pause_thread(int n) 
{
  std::this_thread::sleep_for (std::chrono::seconds(n));
  std::cout << "pause of " << n << " seconds ended , ID = " << std::this_thread::get_id()  << std::endl;
}
 
int main() 
{
  std::cout << "Spawning 2 threads...\n";
  std::thread t1 (pause_thread,1);
  std::thread t2 (pause_thread,2);


  std::cout << "Done spawning threads. Now waiting for them to join:\n";
  //t1.join();
  t2.join();

  //t1.detach();
  //t2.detach();

  std::cout << "All threads joined!\n";

  return 0;
}

Actual Output :

Spawning 2 threads...
Done spawning threads. Now waiting for them to join:
pause of 1 seconds ended , ID = 10218
pause of 2 seconds ended , ID = 59306
All threads joined!

===================

Update:

Thanks to all comments and answers from everyone.

Now, I understand my incorrect logic because I mistakenly thought that a thread were executed only when the join() or detach() function is called.

I realize that a thread is executed as soon as it is created without the need to call join() or detach().

join() : In the main() function, after we call join() on a thread, the main() function will be blocked until the thread completes. After the thread completes, the main() function will resume, and the output from the thread will be shown along with any remaining output from the main() function.

detach() : In the main() function, if we call detach() on a thread, then, both the main() function and thread can run concurrently, and they are not blocked by each other or do not depend on each other in any way. In this case, if the main() function finishes before the detached thread completes, then we can only see the output from the main() function and NOT from the detached thread. However, if the detached thread finishes before the main() function completes, then we can see the output from both the thread and the main() function.

Job_September_2020
  • 858
  • 2
  • 10
  • 25
  • 2
    Can you explain, in detail, why you believe that "only thread 2 would be executed"? What do you believe is the reason for that? The reason the shown code creates two threads is because that's literally what the shown code does: creates two threads. I can see how the shown code can result in undefined behavior, in an edge case, but the shown code creates two `std::thread` objects. The End. So it starts two threads, at the very least, even in the situation that results in that edge case, and undefined behavior. What, exactly, led you to conclude one of those threads never gets created? – Sam Varshavchik Jan 23 '22 at 03:59
  • It is not clear why you believe creating 2 threads will run a number of threads that is not 2. – Drew Dormann Jan 23 '22 at 04:02
  • 2
    Are you under the impression that a thread doesn't start work until `t1.join();` is called? `join` actually means "block the current thread until `t1` finishes". But `t1` starts as soon as it's created. – Nathan Pierson Jan 23 '22 at 04:02
  • Thanks for your comment. Here is my answer. If in the code, I remove both lines "t1.join(); and "t2.join();", then the output does not show "pause of 1 seconds ended , ID = 10218" and "pause of 2 seconds ended , ID = 59306". I thought this means that the thread should be executed only if we call the function join(). If I don't call the function join(), the thread is not executed. Am I wrong here ? – Job_September_2020 Jan 23 '22 at 04:03
  • 2
    `join` is a guarantee. Lack of `join` guarantees nothing. It's just a matter of which thread the OS wants to run (if any) up until the main thread hits its `return 0` – Silvio Mayolo Jan 23 '22 at 04:05
  • @Silvio Mayolo, Thanks for you comment. But, If I don't call join() for both threads, then the output does not seem to show the output from that the function "pause_thread()" from any thread. Does it mean that the thread is executed but the output is shown outside the main thread ? – Job_September_2020 Jan 23 '22 at 04:08
  • 1
    Take a look at [this question](https://stackoverflow.com/questions/22803600/when-should-i-use-stdthreaddetach/22804813). Essentially, `t2.join();` is saying "wait for `t2` to finish before proceeding further". And by the time `t2` is finished, `t1` is also finished. (Well, not _necessarily_, but probably.) If you _don't_ have a join, and also you don't detach either thread, then when main returns they're unceremoniously terminated without necessarily completing their work. – Nathan Pierson Jan 23 '22 at 04:10
  • @NathanPierson, Let me check out your link. Thanks. It seems I begin to see your point here... I appreciate that. – Job_September_2020 Jan 23 '22 at 04:12
  • 1
    Perhaps you could [edit] this question to clarify that it's asking about what `join` does. I had read this question earlier and did not guess that `join` was in any way a source of your confusion. – Drew Dormann Jan 23 '22 at 04:14
  • @DrewDormann, Good point. I will update. – Job_September_2020 Jan 23 '22 at 04:15
  • Thanks to all. Now, I understand how the join() function works, and how a thread is executed even without the join() function being called because a thread is executed as soon as it is created. – Job_September_2020 Jan 23 '22 at 04:20
  • 1
    A side note : you might also want to look into std::async if you want better abstraction and a build in mechanism to get information back from threads you have started (e.g. starting functions that do not return a void and/or can throw exceptions). – Pepijn Kramer Jan 23 '22 at 04:58
  • @PepijnKramer, Yes. I will look into that. Currently, I am learning about threads and Async along with Promise and Future. There are so many exciting topics. – Job_September_2020 Jan 23 '22 at 07:09
  • _“Now, I realize that ... if we call join(), then we can see the output of the thread from the main() function.“_ That is not correct either. It is possible to see output without calling `join`. "Join" doesn't mean "start the thread" and "join" doesn't mean "show output". – Drew Dormann Jan 23 '22 at 14:51
  • @DrewDormann, I knew what you meant. I understood what join() does, and I'll make that point clearer. After we call join() on a thread, the main() function will be blocked and wait for the thread to finish before the main() function can continue. So, because of the join() call, after the thread is finished, the main() function will resume, and the output from the thread will be shown along with the output from the main function. – Job_September_2020 Jan 25 '22 at 22:38

1 Answers1

7

Threads start execution not when .join() is called on them, but when they are constructed.

.join() is used to block the calling thread's execution until the thread being joined to has finished execution. It is typically required to join all threads before main() exits for the reason you observed:

When main() reaches the return statement, the scope of the std::thread objects is left. If neither .join() or .detach() has been called on the std::thread managing a thread at this point, the std::thread's destructor is going to call std::terminate() which (by default) aborts the whole program's execution.

Even if you detach the threads instead of joining them, when main() didn't block long enough via calls to .join() and exits before the threads have finished their output, there is a problem. After exiting main(), static storage duration objects are destroyed. One such object is std::cout. When the threads then try to output via std::cout after their wait, it is already destroyed and the program has undefined behavior.

Also note that detaching t1 and joining t2 isn't safe either, although the waiting times seem to suggest that. When exactly threads are scheduled for execution is up to the operating system. It could happen that thread t2 finishes before t1, in which case the issue mentioned above applies again, causing the program to again have undefined behavior.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
user17732522
  • 53,019
  • 2
  • 56
  • 105
  • BTW, I know that join() will block the main() function while a thread is executed. And, detach() will not block the main function while the thread is running because both the thread and main function will run concurrently. Now, if I don't call join() or detach(), will the thread block the main function or run concurrently with the main function ? Or what else happens ? – Job_September_2020 Jan 23 '22 at 04:29
  • @Job_September_2020 I was trying to convey that in my answer. `main` will simply continue execution until it is done. But then when it is done the destructor of `std::thread` is called and it will abort the program if the thread hasn't been joined or detached. – user17732522 Jan 23 '22 at 04:33