文章目录
- 1. 目的
- 2. 什么是线程泄漏
- 3. pthread 线程泄漏例子
- 3.1 代码
- 3.2 编译和运行
- 3.3 简要分析
- 4. 检测线程泄漏
- 4.1 编译链接时传入参数 `-fsanitize=thread`
- 4.2 确认 TSAN_OPTIONS 环境变量
- 5. 修复线程泄漏
- 5.1 方法1: 主线程等待子线程
- 5.2 方法2:子线程主动脱离主线程
1. 目的
线程泄漏是使用多线程时容易出现的一个错误, 这和内存泄漏有点像:操作系统为创建过程消耗了资源,但没有回收, 如果频繁的创建那么系统被严重拖累, 新申请线程或内存的成功概率降低。
2. 什么是线程泄漏
使用 pthread 实现多线程任务调度时, 子线程是从主线程创建的, 如下两种情况都满足时,会产生线程泄漏:
- 子线程执行的函数中没有显式的销毁子线程 (未执行
pthread_detach()
) - 主线程中也没有等待子线程的结束 (未执行
pthread_join()
)
3. pthread 线程泄漏例子
3.1 代码
这里简单起见, 线程函数 f()
采用极简方式设定:传空指针参数, 只打印一句话和返回空指针。
// 这是一个反面例子。#include <cstddef>
#include <pthread.h>
#include <stdio.h>void* f(void*)
{printf("Hello from thread function f\n");return NULL;
}int main()
{pthread_t t;pthread_create(&t, NULL, f, NULL); // 主线程里开启子线程 tprintf("This is main thread\n");return 0;
}
3.2 编译和运行
zz@Legion-R7000P% clang++ leak_example.cpp
第一次运行
zz@Legion-R7000P% ./a.out
Hello from thread function f
This is main thread
第二次运行
zz@Legion-R7000P% ./a.out
This is main thread
3.3 简要分析
由于主线程(main()
函数)没有等待子线程的结束, 程序运行可能出现的情况有两种:
- 打印了子线程的内容
- 没打印子线程的内容
以上两种结果都可能出现, 不能认为能出现正确结果就万事大吉了, 需要严格确保结果的正确性。
4. 检测线程泄漏
使用 ThreadSanitizer 可以检查 Pthread 的内存泄漏。需要的步骤如下
4.1 编译链接时传入参数 -fsanitize=thread
clang++ leak_example.cpp -fsanitize=thread
4.2 确认 TSAN_OPTIONS 环境变量
第一种: TSAN_OPTIONS
环境变量为空。
第二种: 设定为检测线程泄漏: export TSAN_OPTIONS="report_thread_leaks=1"
以上两种情况,都可以让线程泄漏的问题在程序运行阶段被报告出来。
而如果设定了 export TSAN_OPTIONS="report_thread_leaks=0", 则无法报告线程泄漏问题。
5. 修复线程泄漏
5.1 方法1: 主线程等待子线程
#include <cstddef>
#include <pthread.h>
#include <stdio.h>void* f(void*)
{printf("Hello from thread function f\n");return NULL;
}int main()
{pthread_t t;pthread_create(&t, NULL, f, NULL); // 主线程里开启子线程 tpthread_join(t, NULL); // 主线程等待子线程 t 的结束return 0;
}
5.2 方法2:子线程主动脱离主线程
用到了 pthread_detach()
这个 API:
应用到代码中:
#include <cstddef>
#include <pthread.h>
#include <stdio.h>void* f(void*)
{pthread_detach(pthread_self()); // 子线程主动 detachprintf("Hello from thread function f\n");return NULL;
}int main()
{pthread_t t;pthread_create(&t, NULL, f, NULL); // 主线程里开启子线程 treturn 0;
}