In the Linux system, threads are lightweight execution units, the correct destruction of threads can avoid memory leaks and other problems, Linux threads have joinable and detached two kinds of properties.

In my previous article, I covered some historical background of Linux threads and the NPTL thread model. In this article, the correct way to destroy Linux threads will be explained with the above knowledge in mind.

In Linux, threads are lightweight execution units, and destroying them properly can avoid memory leaks and other problems. The reason for writing this article is that the production environment reported some memory leaks, and after troubleshooting, we found that it was because the threads were not destroyed correctly: the threads were used somewhere, and would only be triggered once during normal testing, but when they came to the line, the user might not close the app for a very long time, and this time was long enough to eventually cause a rather obvious memory leak.

Recall that there are several functions related to the Linux thread life cycle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
    const pthread_attr_t * _Nullable __restrict,
    void * _Nullable (* _Nonnull)(void * _Nullable),
    void * _Nullable __restrict);

void pthread_exit(void * _Nullable);
int pthread_cancel(pthread_t);

int pthread_detach(pthread_t);
int pthread_join(pthread_t , void * _Nullable * _Nullable);

Several ways to exit a thread

There are several ways to make a thread exit at the end of a thread.

Direct return

When a thread function reaches the end of its execution or encounters a return statement, the thread will exit automatically. This is the most common way to exit a thread.

pthread_exit

Calling pthread_exit immediately terminates the execution of the current thread. The pthread_exit function takes a pointer argument, which is passed as the thread’s return value to pthread_join the thread’s other threads.

Unlike direct returns, pthread_exit can be called anywhere the thread logic requires, not just the thread’s entry function. If pthread_exit is called in the main function, it will cause the process to exit.

pthread_cancel

Calling pthread_cancel sends a cancellation request to the specified thread. Unlike pthread_exit, which can only terminate the thread, pthread_cancel can be called by another thread to explicitly initiate a cancellation request to the target thread, and pthread_cancel is not mandatory, but rather is a negotiation mechanism whereby the target thread has the choice of whether or not to respond to the cancellation.

There are several APIs related to pthread_cancel:

1
int pthread_setcancelstate(int , int * _Nullable);

Sets the cancellability state of the current thread, the available values are PTHREAD_CANCEL_ENABLE and PTHREAD_CANCEL_DISABLE.

1
int pthread_setcanceltype(int , int * _Nullable);

Sets the handling of the current thread when it receives a cancellation. The available values are PTHREAD_CANCEL_DEFERRED (terminate the thread when it continues to the cancellable point) and PTHREAD_CANCEL_ASYNCHRONOUS (terminate immediately).

1
void pthread_testcancel(void);

Creates a PTHREAD_CANCEL_ENABLE point. Only makes sense if the cancellable state is PTHREAD_CANCEL_ENABLE and the cancellation processing is PTHREAD_CANCEL_DEFERRED, which will terminate the thread.

joinable and detached threads

Linux threads have two attributes: joinable and detached, which determine what happens to a thread’s resources after it exits.

joinable

joinable is the default property of threads. joinable threads do not release their resources (mainly thread-related memory structures, thread descriptors, etc.) until another thread waits and reclaims the thread’s resources by calling the pthread_join function. If the pthread_join function is not called, the joinable thread will become a “zombie thread” after exiting, causing memory leaks and other problems.

The following is an example of the use of the joinable attribute thread:

1
2
3
4
5
6
7
pthread_t pth;
int ret = pthread_create(&pth, NULL, chuanFunc, NULL);
if (ret == 0) {
    void* thread_result;
    //Block the current thread, wait for the child thread to exit and get the exit status.
    pthread_join(pth, &thread_result);
}

detached

When a thread is set to the detached attribute, its exit status and resources are automatically reclaimed by the system without the need for other threads to call the pthread_join function and wait for the thread to exit. detached threads are used in situations where you don’t need to care about the exit status of the thread or clean up work, which can improve the efficiency of the application and simplify the logic of the code.

The following is an example of the detached attribute thread:

1
2
3
4
5
pthread_t pth;
int ret = pthread_create(&pth, NULL, chuanFunc, NULL);
if (ret == 0) {
    pthread_detach(pth);//The call returns immediately and does not wait for the child thread to finish executing.
}

You can also configure the detached property when the thread is created, as follows:

1
2
3
4
5
6
7
8
#include <pthread.h>

pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
//pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//Setting the detached attribute
pthread_create(&thread, &attr, chuanFunc, args);

Properties are only valid if they are set before the thread is created; once the thread is created, it is not possible to change its properties.

Best practices

  • It is good programming practice to ensure that allocated memory and file descriptors are properly freed and closed at the end of a thread function, whether it is a joinable or detached thread;
  • After pthread_create, use pthread_join or pthread_detach as needed to ensure that thread structures, etc. are properly freed.