检测和避免 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(),该方法可联接每个可接合线程。

检测泄漏

如同其他内存泄漏中一样,进程启动时问题可能没那么明显。这里介绍一种无需访问源代码便可检测此类问题的方法:

  1. 计算进程中线程堆栈的数量。这包括正在运行的活动线程和已终止线程的数量。
  2. 计算进程中正在运行的活动线程的数量。
  3. 比较两者。如果现有线程堆栈的数量大于正在运行的活动线程的数量,且在程序运行时这两个数字的差量在不断增加,那么内存在泄漏。

这种内存泄漏很有可能是因未能联接可接合线程而造成的。

使用 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 线程编程中避免内存泄漏的更多相关文章

  1. 在 JNI 编程中避免内存泄漏

    JAVA 中的内存泄漏 JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏:JVM 内存中 native memory 的内存泄漏. Java H ...

  2. 在 JNI 编程中避免内存泄漏与崩溃

    JNI 编程简介 JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中 ...

  3. Posix线程编程指南(4) 线程终止

    线程终止方式 一般来说,Posix的线程终止有两种情况:正常终止和非正常终止.线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式:非正常终止是 ...

  4. Posix线程编程指南

    Posix线程编程指南 Posix线程编程指南... 1 一线程创建与取消... 2 线程创建... 2 1.线程与进程... 2 2. 创建线程... 2 3. 线程创建属性... 2 4. 创建的 ...

  5. Posix线程编程指南(4)

    Posix线程编程指南(4) 杨沙洲 原文地址:http://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part4/ 线程终 ...

  6. Java中关于内存泄漏出现的原因以及如何避免内存泄漏

    转账自:http://blog.csdn.net/wtt945482445/article/details/52483944 Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是静 ...

  7. Posix线程编程指南(1)

    这是一个关于Posix线程编程的专栏.作者在阐明概念的基础上,将向您详细讲述Posix线程库API.本文是第一篇将向您讲述线程的创建与取消.   一.线程创建 1.1 线程与进程相对进程而言,线程是一 ...

  8. 关于Hash集合以及Java中的内存泄漏

    <学习笔记>关于Hash集合以及Java中的内存泄漏 标签: 学习笔记内存泄露hash 2015-10-11 21:26 58人阅读 评论(0) 收藏 举报  分类: 学习笔记(5)  版 ...

  9. 系统剖析Android中的内存泄漏

    [转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...

随机推荐

  1. MongoDB - 启动&连接数据库

    1> 启动数据库 1.1> 依次添加如下目录: 1.1.1> mongodb-space 1.1.2> mongodb-space/conf 1.1.3> mongodb ...

  2. python入门:while循环里面True和False的作用,真和假

    #!/usr/bin/env python # -*- coding:utf-8 -*- #while循环里面True和False的作用,真和假 """ n1等于真(Tr ...

  3. destoon 多表联合查询时出现解析错误,parse_str函数解析错误

    数据库前缀  wb_ 标签 ,调用文章时获取评论数量 <!--{php $tags=tag("table=article_24 a left join wb_comment_stat ...

  4. ccf 201712-4 行车路线(Python实现)

    一.原题 问题描述 试题编号: 201712-4 试题名称: 行车路线 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 小明和小芳出去乡村玩,小明负责开车,小芳来导航. 小芳将 ...

  5. 第3-5课 填充左侧菜单/品牌的添加 Thinkphp5商城第四季

    目录 左侧菜单的填充 品牌的添加 form标签里要加上method="post" enctype="multipart/form-data" form标签里如果 ...

  6. STM32CUBEMX入门学习笔记2:关于STM32芯片使用内部flash

    找到正点原子的官网,下载他的HAL库:http://www.openedv.com/thread-109778-1-1.html 找到此例程,并打开其工程文件. 找到此文件,复制到自己工程里 复制到自 ...

  7. leetcode-4-basic

    解题思路:这道题比较简单,代码不贴了.需要注意的是: 数字与字符串之间的转换, char str[100]; sprintf(str, "%d", num); 解题思路: 这道题是 ...

  8. HDU 5111 Alexandra and Two Trees 树链剖分 + 主席树

    题意: 给出两棵树,每棵树的节点都有一个权值. 同一棵树上的节点的权值互不相同,不同树上节点的权值可以相同. 要求回答如下询问: \(u_1 \, v_1 \, u_2 \, v_2\):询问第一棵树 ...

  9. Linux性能查看

    1.TOP top  登录后默认按进程的CPU使用情况排序,  按M则按内存使用排序 2. vmstat 2 2 显示系统负载 3. free  -m 查看内存使用情况 4.抓包 tcpdump -i ...

  10. 中国首届CSS开发者大会讲师照片

    中国首届CSS开发者大会讲师照片 Bert Bos Winter 点头猪 灭灭 jaychsu Hax 尤雨溪 一丝 勾三股四 小倩 **