在 POSIX 线程编程中避免内存泄漏
检测和避免 POSIX 线程内存泄漏的技巧
POSIX 线程(pthread)编程定义了一套标准的 C 编程语言类型、函数和常量 — 且 pthreads 提供了一种强大的线程管理工具。要充分使用 pthreads,您要避免常见错误。一个常见的错误就是忘记联接可接合的线程,从而导致内存泄漏并增加工作量。在该篇技巧型文章中,学习 POSIX 线程基础,了解如何识别和检测线程内存泄漏,并获得避免出现这种情况的可靠建议。
POSIX 线程简介
使用线程的主要原因是要提高程序性能。线程的创建和管理只需要较小的操作系统开销和较少的系统资源。一个进程内的所有线程共享相同的地址空间,使得线程间的通信更高效,且比进程间通信更易于实现。例如,如果一个线程在等待一个输入/输出系统调用完成,其他线程可以处理 CPU 密集型任务。通过线程,可以优先调度重要任务 — 甚至中断 — 低优先级任务。可将偶尔发生的任务放在定期调度的任务之间,创建调度灵活性。最后,pthreads 是在多 CPU 计算机上进行并行编程的理想之选。
而且使用 POSIX 线程或 pthreads 的主要原因更加简单:作为标准 C 语言线程编程接口的一部分,它们可高可移植的。
POSIX 线程编程有诸多优势,但是如果您不明确一些基本规则,就有可能编写一些难以调试的代码并造成内存泄漏。我们首先回顾一下 POSIX 线程,分为可接合线程 或分离线程。
可接合线程
如果您希望生成一个新的线程,且需要知道它是如何终止的,那么您需要一个可接合线程。对于可接合线程,系统分配专用存储器来存储线程终止状态。线程终止后状态得到更新。要获得线程终止状态,调用 pthread_join(pthread_t thread, void** value_ptr)。
系统为每个线程分配底层存储,包括堆栈、线程 ID、线程终止状态等。这个底层存储将一直保留在进程空间(且不能回收),直至线程终止并为其他线程所联接。
分离线程
大多数时候,您只需创建一个线程,向其分配一些任务,然后继续处理其他事务。在这些情况下,您不关注线程是如何终止的,这时使用分离线程是一个很好的选择。
对于分离线程,在线程终止后系统自动回收其底层资源。
识别泄漏
如果您创建一个可接合的线程,但是忘记联接它,其资源或私有内存一直保存在进程空间中,从未进行回收再利用。一定要联接可接合的线程;否则,可能会引起严重的内存泄漏问题。
例如,Red Hat Enterprise Linux (RHEL4)上的一个线程需要一个 10MB 的堆栈,这意味着,如果不联接它,会有至少 10MB 的内存泄漏。假设您设计一个管理器-工作线程模式的程序来处理传入的请求。然后需要创建越来越多的工作线程来执行各个任务,最后终止这些线程。如果它们是可接合的线程,且您没有调用 pthread_join() 来联接它们,那么在线程终止后,每个产生的线程都将泄漏大量的内存(至少每堆栈 10MB)。随着创建并在未联接的情况下终止的工作线程越来越多,泄漏的内存量也持续增加。另外,进程将无法创建新的线程,因为无内存可供创建新线程使用。
清单 1 显示在忘记联接可接合线程时引发的严重内存泄漏。您还可以使用该代码检查可在一个进程空间中共存的线程体的最大量。
清单 1. 引发内存泄漏
#include<stdio.h>
#include<pthread.h>
void run() {
pthread_exit(0);
} int main () {
pthread_t thread;
int rc;
long count = 0;
while(1) {
if(rc = pthread_create(&thread, 0, run, 0) ) {
printf("ERROR, rc is %d, so far %ld threads created\n", rc, count);
perror("Fail:");
return -1;
}
count++;
}
return 0;
}
清单 1 中调用了 pthread_create() 来创建一个含默认线程属性的新线程。默认情况下,新创建的线程是可接合的。它不断创建新的可接合线程,直至有故障发生。然后输出错误代码和故障原因。
使用以下命令在 Red Hat Enterprise Linux Server 5.4 上编译清单 1 中的代码时:[root@server ~]# cc -lpthread thread.c -o thread, 您将获得清单 2 所示的结果。
清单 2. 内存泄漏结果
[root@server ~]# ./thread
ERROR, rc is 12, so far 304 threads created
Fail:: Cannot allocate memory
在代码创建了 304 个线程之后,它无法创建更多线程。错误代码是 12,这表示无更多内存可用。
如清单 1 和清单 2 所示,虽然生成了可接合线程,但是却未将其联接,因此每个终止的可接合线程仍然占用进程空间,泄漏进程内存。
RHEL 上的一个 POSIX 线程拥有一个大小为 10MB 的私有堆栈。换言之,系统为每个 pthread 分配至少 10MB 的专用存储。在我们的示例中,304 个线程是在进程停止前创建的;这些线程占用 304*10MB 内存,合计约 3GB。一个进程的虚拟内存的大小是 4GB,其中四分之一的进程空间是为 Linux 内核预留的。这样一来,就有 3GB 的内存空间可用作用户空间。因此,3GB 内存由死线程消耗。这是很严重的内存泄漏。而且很容易理解它发生的速度为何如此之快。
要修复泄漏,您可以添加代码调用 pthread_join(),该方法可联接每个可接合线程。
检测泄漏
如同其他内存泄漏中一样,进程启动时问题可能没那么明显。这里介绍一种无需访问源代码便可检测此类问题的方法:
- 计算进程中线程堆栈的数量。这包括正在运行的活动线程和已终止线程的数量。
- 计算进程中正在运行的活动线程的数量。
- 比较两者。如果现有线程堆栈的数量大于正在运行的活动线程的数量,且在程序运行时这两个数字的差量在不断增加,那么内存在泄漏。
这种内存泄漏很有可能是因未能联接可接合线程而造成的。
使用 pmap 计算线程堆栈数
在一个运行的进程中,线程堆栈的数量等于进程中线程体的数量。线程体包括运行的活动线程和可接合的死线程。
pmap 是一种用于汇报进程内存的 Linux 工具。结合使用以下命令来获取线程堆栈数:
[root@server ~]# pmap PID | grep 10240 | wc -l
(10240KB 是 Red Hat Enterprise Linux Server 5.4 上的默认堆栈大小。)
使用 /proc/PID/task 计算活动线程数
每次创建一个线程且该线程在运行时,会有一个条目填充到 /proc/PID/task 中。当线程终止时,不管该线程是可接合的还是分离的,都会将该条目从 /proc/PID/task 中删除。因此活动线程数可通过运行以下命令得出:
[root@server ~]# ls /proc/PID/task | wc -l.
比较输出
检查 pmap PID | grep 10240 | wc -l 的输出并将其与 ls /proc/PID/task | wc -l 的输出进行比较。如果所有线程堆栈的数量大于活动线程的数量,且在程序运行时两者的差量在持续增长,您可以确定内存泄漏问题确实存在。
预防泄漏
在编程过程中应当联接可接合线程。如果您在程序中创建可接合的线程,切勿忘记调用pthread_join(pthread_t, void**) 来回收分配给线程的专用存储。否则,将引发严重的内存泄漏问题。
在编程后的测试阶段,您可以使用 pmap 和 /proc/PID/task 检测这种泄漏是否存在。如果确实存在,检查源代码,看是否联接了所有可接合线程。
就这些内容。只需少量预防工作即可为您省掉大量后续工作,避免令人头疼的内存泄漏问题。
在 POSIX 线程编程中避免内存泄漏的更多相关文章
- 在 JNI 编程中避免内存泄漏
JAVA 中的内存泄漏 JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏:JVM 内存中 native memory 的内存泄漏. Java H ...
- 在 JNI 编程中避免内存泄漏与崩溃
JNI 编程简介 JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中 ...
- Posix线程编程指南(4) 线程终止
线程终止方式 一般来说,Posix的线程终止有两种情况:正常终止和非正常终止.线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式:非正常终止是 ...
- Posix线程编程指南
Posix线程编程指南 Posix线程编程指南... 1 一线程创建与取消... 2 线程创建... 2 1.线程与进程... 2 2. 创建线程... 2 3. 线程创建属性... 2 4. 创建的 ...
- Posix线程编程指南(4)
Posix线程编程指南(4) 杨沙洲 原文地址:http://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part4/ 线程终 ...
- Java中关于内存泄漏出现的原因以及如何避免内存泄漏
转账自:http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静 ...
- Posix线程编程指南(1)
这是一个关于Posix线程编程的专栏.作者在阐明概念的基础上,将向您详细讲述Posix线程库API.本文是第一篇将向您讲述线程的创建与取消. 一.线程创建 1.1 线程与进程相对进程而言,线程是一 ...
- 关于Hash集合以及Java中的内存泄漏
<学习笔记>关于Hash集合以及Java中的内存泄漏 标签: 学习笔记内存泄露hash 2015-10-11 21:26 58人阅读 评论(0) 收藏 举报 分类: 学习笔记(5) 版 ...
- 系统剖析Android中的内存泄漏
[转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...
随机推荐
- 安装mysqlclient失败
环境:python3.6 sudo apt-get install python3.6-dev sudo apt-get install default-libmysqlclient-dev 参考:h ...
- 12Vim在系统配置中的应用示例
Vim 在系统配置中的应用示例 1. 配置主机名称 为了便于咱局域网中查找某台特定的主机,后者对主机进行区分,除了要有IP地址外,还要为主机配置一个主机名,主机名之间可以通过这个类似于域名的名称来相互 ...
- Ubuntu 开机启动不执行
解决方案: 1.将/etc/rc.local的命令改成更加兼容的模式,将"#!/bin/sh"改为"#!/bin/bash" 2.将/bin/sh重新链接到/b ...
- css3属性:美化表单、点击元素产生的背景与边框怎么去掉,滚动回弹效果
- DeepFaceLab小白入门(1):软件简介!
简介 DeepFaceLab是一种利用深度学习识别和交换图片和视频中的人脸的工具 这是一个github上的开源项目,所有人都可以查看源代码也能免费使用.个人认为这个项目的最大优点就是安装超级简单,几乎 ...
- Thinkphp 5 调试执行的SQL语句
在模型操作中 ,为了更好的查明错误,经常需要查看下最近使用的SQL语句,我们可以用getLastsql方法来输出上次执行的sql语句.例如: User::get(1); echo User::getL ...
- Docker工具
虚拟化 什么是虚拟化 在计算机中,虚拟化(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源, 如服务器.网络.内存及存储等,予以抽象.转换后呈现出来, 打破实体结构间的 ...
- Python中的属性访问与描述符
Python中的属性访问与描述符 请给作者点赞--> 原文链接 在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作.例如,有一个类实例对象foo,它有一个nam ...
- JS中如何操作数组
背景:随笔中所应用到的代码来自于上一篇随笔,MVC&JQuery如何根据List动态生成表格,部分代码不再重复. 代码如下: $("#btnTan").click(func ...
- action属性和data属性组合事例