4

销毁Linux线程的正确方式

 11 months ago
source link: https://kanchuan.com/blog/177-thread-destory.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

销毁Linux线程的正确方式

原创  2023-06-20 

在Linux系统中,线程是轻量级的执行单元,正确销毁线程可避免内存泄漏等问题。Linux 线程的有 joinable 和 detached 两种属性。

在之前的文章《如何正确地获取线程ID》中,我介绍了Linux线程的一些历史背景以及NPTL线程模型。在本文中,将以上述知识点为背景,讲解销毁Linux线程的正确方式。

在Linux系统中,线程是轻量级的执行单元,正确销毁线程可避免内存泄漏等问题。写这篇文章的起因是线上报告一些内存泄漏的情况,排查后发现是因为线程没有正确销毁导致的:有一处开启线程的逻辑,在正常测试时仅会出发一次,但是到线上后,用户可能很长时间都不会关闭App,这个时间足够长,以至于积少成多,最终造成了比较明显的内存泄漏。

回顾下Linux线程生命周期相关的有几个函数:

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

退出线程的几种方式

在线程结束时,有几种方式可以使线程退出:

当线程函数执行到末尾或者遇到 return 语句时,线程会自动退出。这是最常用的线程退出方式。

pthread_exit

调用 pthread_exit 会立即终止当前线程的执行,并将线程的退出状态回传给它的 pthread_join 线程。如有 pthread_cleanup_push 注册的清理函数,pthread_exit还会触发调用清理函数,最后释放线程所占用的资源,比如线程栈空间等。

pthread_exit 可以在线程逻辑任意需要的地方调用,不仅仅局限于线程函数的末尾。如果在主函数中调用了 pthread_exit,将导致进程退出。

pthread_cancel

调用 pthread_cancel 函数会向指定的线程发送取消请求。与 pthread_exit 只能终止本线程不同,pthread_cancel 可以由其他线程调用以显式地向目标线程发起取消请求,且 pthread_cancel 不是强制的,目标线程可以选择是否响应这次取消操作,另外,使用 pthread_cancel 方式终止线程需要目标线程手动处理资源的释放。

与 pthread_cancel 相关的几个API:

int pthread_setcancelstate(int , int * _Nullable);

设置当前线程的“可取消性”状态,可选状态值分别为:PTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DISABLE

int pthread_setcanceltype(int , int * _Nullable);

设置当前线程的接收取消时的处理。可选值为:PTHREAD_CANCEL_DEFERRED(继续运行至「可取消点」时终止线程) 和 PTHREAD_CANCEL_ASYNCHRONOUS(立即终止)。

void pthread_testcancel(void);

创建一个「可取消点」。只有可取消状态为PTHREAD_CANCEL_ENABLE,且取消处理为PTHREAD_CANCEL_DEFERRED时才有意义,将终止线程。

线程的 joinable 和 detached

Linux线程的有两种属性:joinable 和 detached。这些属性决定了线程退出后如何处理其状态和释放资源。

joinable

Joinable 是线程的默认属性。当线程被设置为 Joinable 属性时,可以通过调用 pthread_join 函数等待目标线程的退出,并获取目标线程的退出状态。

Joinable 属性的线程在退出后,不会自动回收其状态和资源,必须被其他线程显式地调用 pthread_join 函数来同步等待该线程执行完成,并进行清理工作。如果不调用 pthread_join 函数,那么 Joinable 线程将在执行完任务后成为"僵尸线程(zombie thread)",会造成内存泄漏等问题。

下面是 Joinable 属性线程的使用示例:

pthread_t pth;
int ret = pthread_create(&pth, NULL, chuanFunc, NULL);
if (ret == 0) {
    // 同步等待子线程退出并获取退出状态,会阻塞当前线程
    void* thread_result;
    pthread_join(pth, &thread_result);
}

detached

当线程被设置为 detached 属性时,它的退出状态和资源会被系统自动回收,无需显式地调用 pthread_join 函数等待线程退出。detached 线程适用于不需要关心线程退出状态或进行清理工作的情况,可以提高程序的效率和简化代码逻辑。

下面是 detached 属性线程的使用示例:

pthread_t pth;
int ret = pthread_create(&pth, NULL, chuanFunc, NULL);
if (ret == 0) {
    pthread_detach(pth);//调用会立即返回,不会等待子线程执行结束
}

也可以在线程创建时,配置 detached 属性,如下:

#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);//设置detached属性
pthread_create(&thread, &attr, chuanFunc, args);

只有在线程创建之前设置属性才有效,一旦线程创建,无法更改其属性。

我们不需要关注子线程的退出状态时,可以使用 detached 属性的线程方便处理,此时不需要手动调用 pthread_exit 管理线程本身的资源销毁。

相关文章:

阿里云云盘扩容笔记
Linux后台任务执行
如何正确地获取线程ID?
inode用尽导致磁盘空间不足


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK