Take a quick look at the effects of main thread exits on child threads in C

  • 2020-06-23 01:18:06
  • OfStack

This article mainly introduces a simple understanding of the main thread exit in C language on the impact of child threads, through the example code introduced in detail, for everyone's study or work has a certain reference learning value, you can refer to the friends in need

For a program, if the main process exits before the child terminates, the Linux kernel changes the child's parent, ID, to 1 (that is, the init process), and the child is reclaimed by the init process when the child terminates.

What happens if you replace the process with a thread? Suppose that the main thread exits before the child ends, what happens to the child?

In some forums, I have seen many people saying that child threads will also exit, which is actually wrong, because they confuse the concept of thread exit and process exit. The real answer is that the state of the child thread after the main thread exits depends on the process it is in, and if the process does not exit, the child thread is still functioning. If a process exits, all of its threads exit, and so do its children.

Exit the main line first

Let's take a look at an example where the mainline exits first:


#include <pthread.h>
#include <unistd.h>

#include <stdio.h>

void* func(void* arg)
{
  pthread_t main_tid = *static_cast<pthread_t*>(arg);
  pthread_cancel(main_tid);
  while (true)
  {
    //printf("child loops\n");
  }
  return NULL;
}

int main(int argc, char* argv[])
{
  pthread_t main_tid = pthread_self();
  pthread_t tid = 0;
  pthread_create(&tid, NULL, func, &main_tid);
  while (true)
  {
    printf("main loops\n");
  }
  sleep(1);
  printf("main exit\n");
  return 0;
}

Passes the thread number of the main thread to the child thread and terminates the main thread with pthread_cancel in the child thread. Running the program, you can see that after printing a fixed number of "main loops" the program hangs, but does not exit.

The main thread has terminated because of the quilter thread. All prints of "main exit" are not seen. The child thread terminates the main thread and enters the loop while, so the program looks suspended. If we let the print statement in the subprocess while loop take effect before running, we will find that the program will print "child loops" directly.

The main thread subthreads terminate, but the process on which they depend does not exit, so the subthreads continue to function normally.

The main thread exits with process 1

I've seen people say that if the mainline exits first, the child will exit, but they confuse thread exit with process exit. The following example illustrates their point:


void* func(void* arg)
{
  while (true)
  {
    printf("child loops\n");
  }
  return NULL;
}

int main(int argc, char* argv[])
{
  pthread_t main_tid = pthread_self();
  pthread_t tid = 0;
  pthread_create(&tid, NULL, func, &main_tid);
  sleep(1);
  printf("main exit\n");
  return 0;
}

Run the above code and you will see that the program exits after printing 1 fixed number of "child loops" and 1 sentence of "main exit", and the last sentence before exiting is printed as "main exit".

According to their logic, you see, since the main thread quits after printing "main exit", then the child threads follow, so there are no child threads to print.

But this is a confusion between process exit and thread exit. The actual situation is that main function in the main thread executes ruturn and then calls glibc library function exit. exit calls _exit system call to exit the process after the related cleaning work. So, in this case, the process exits at the end of the run causing all threads to exit, not the main thread exits causing the child threads to exit.

Linux threading model

In fact, posix threads and 1 of the process is different, there is not the main thread and thread on the concept of points (although there is some distinction between 1) on the actual implementation, if you look closely at apue or unp books will find basic can't see the "main thread" or "child thread" and other words, even in the csapp is done with thread "equivalent" one word to describe the relation between threads.

posix threads after Linux 2.6 are implemented by the user mode pthread library. After using the pthread library, from the user's point of view, there is one thread for every tast_struct (tast_struct was originally the structure of the kernel for one process), and a set of threads and the set of resources they all refer to together are the processes. Starting with Linux 2.6, the kernel had the concept of thread groups, and an tgid (thread group id) field was added to the tast_struct structure. getpid (get process number) also returns tgid from tast_struct via the system call, so tgid is actually the process number. The thread number pid field in tast_struct is obtained by the system call syscall(SYS_gettid).

When a thread receives an kill fatal signal, the kernel applies processing action to the entire thread group. To cope with "signals to process" and "signals to thread", tast_struct maintains two sets of signal_pending, one Shared by thread groups and one unique to threads. Fatal signals sent through kill are placed in signal_pending Shared by thread groups and can be handled by any one thread. Signals sent via pthread_kill are placed in the thread-specific signal_pending and can only be handled by the thread.

apue has this to say about threads and signals:

[

Each thread has its own signal mask, but the processing of the signal is Shared by all threads in the process. This means that although a single thread can block certain signals, when the thread modifies the processing behavior associated with a signal, all threads must share the change in the processing behavior. In this way, if one thread chooses to ignore a signal, the other thread can either restore the signal's default processing behavior or set up a new handler for the signal, thus revoking the signal selection of the above thread.

]

If the default processing action of the signal is to terminate the process, passing the signal to a thread still kills the entire process.

For example, a program a. out creates a child thread, assuming that the thread number of the main thread is 9601 and the thread number of the child thread is 9602 (their tgid are 9601). Because the signal handler is not set by default, if you run the command kill 9602, you can kill the first thread of 9601 and 9602. If you don't know the story behind the Linux thread, you might think you've encountered a supernatural event.

In addition, the thread number obtained by system call syscall(SYS_gettid) is different from the thread number obtained by pthread_self. The thread number obtained by pthread_self is only 1 in the process on which the thread depends. In pthread_self's man page, there is a paragraph like this:

[

Thread IDs are guaranteed to be unique only within a process. A thread ID may be reused after a terminated thread has been joined, or a detached thread has terminated.

]

So the only thread number in the kernel that identifies thread ID can only be obtained through the system call syscall(SYS_gettid).


Related articles: