正确使用pthread_create,防止内存泄漏

转载 2016年12月09日 12:51:01

原文出处:http://www.cnblogs.com/lidabo/p/5514100.html


近日,听说pthread_create会造成内存泄漏,觉得不可思议,因此对posix(nptl)的线程创建和销毁进行了分析。

 
分析结果:如果使用不当,确实会造成内存泄漏。
产生根源:pthread_create默认创建的线程是非detached的。
预防方式:要么创建detached的线程,要么线程线程的start_routine结束之前detached,要么join
 
分析过程如下:
 
  1.查看pthread_create源代码,核心代码如下(nptl/pthread_create.c):

点击(此处)折叠或打开

  1. int
  2. __pthread_create_2_1 (newthread, attr, start_routine, arg)
  3.      pthread_t *newthread;
  4.      const pthread_attr_t *attr;
  5.      void *(*start_routine) (void *); 
  6.      void *arg;
  7. {
  8.   STACK_VARIABLES;
  9.   const struct pthread_attr *iattr = (struct pthread_attr *) attr;
  10.   if (iattr == NULL)
  11.     /* Is this the best idea? On NUMA machines this could mean
  12.        accessing far-away memory. */
  13.     iattr = &default_attr;
  14.   struct pthread *pd = NULL;
  15.   int err = ALLOCATE_STACK (iattr, &pd);//为tcb分配内存
  16.   if (__builtin_expect (err != 0, 0)) 
  17.     /* Something went wrong. Maybe a parameter of the attributes is
  18.        invalid or we could not allocate memory. */
  19.     return err;
  20. //……
  21. err = create_thread (pd, iattr, STACK_VARIABLES_ARGS);//正式创建线程
 
2.查看createthread.c(nptl/sysdeps/pthread/createthread.c)

点击(此处)折叠或打开

  1. static int 
  2. create_thread (struct pthread *pd, const struct pthread_attr *attr,
  3.            STACK_VARIABLES_PARMS)
  4. {
  5. #ifdef TLS_TCB_AT_TP
  6.   assert (pd->header.tcb != NULL);
  7. #endif
  8. //……
  9. int res = do_clone (pd, attr, clone_flags, start_thread,
  10.                   STACK_VARIABLES_ARGS, 1);//clone一个进程
3.接着看start_thread(nptl/pthread_create.c)做了什么

点击(此处)折叠或打开

  1. static int
  2. start_thread (void *arg)
  3. {
  4.   struct pthread *pd = (struct pthread *) arg;
  5. //……
  6.       /* Run the code the user provided. */
  7. #ifdef CALL_THREAD_FCT
  8.       THREAD_SETMEM (pd, result, CALL_THREAD_FCT (pd)); 
  9. #else
  10.       THREAD_SETMEM (pd, result, pd->start_routine (pd->arg)); //正式启动线程的执行,并等待执行完成
  11. #endif
  12. //……
  13. if (IS_DETACHED (pd))
  14.     /* Free the TCB.  */
  15.     __free_tcb (pd);//如果设置detached标志,则释放tcb占用的内容,否则直接返回
  16.   else if (__builtin_expect (pd->cancelhandling & SETXID_BITMASK, 0))
  17.     {
  18.       /* Some other thread might call any of the setXid functions and expect
  19.      us to reply.  In this case wait until we did that.  */
  20.       do
  21.     lll_futex_wait (&pd->setxid_futex, 0, LLL_PRIVATE);
  22.       while (pd->cancelhandling & SETXID_BITMASK);
  23.       /* Reset the value so that the stack can be reused.  */
  24.       pd->setxid_futex = 0;
  25.     }
从上面的过程,我们可以看到,如果在创建线程的时候,如果没有设置detached标志,则tcb内存永远不会释放
 
接下来,我们看看pthread_detach(npth/pthread_detach.c)做了什么

点击(此处)折叠或打开

  1. int
  2. pthread_detach (th)
  3.      pthread_t th; 
  4. {
  5.   struct pthread *pd = (struct pthread *) th; 
  6.   /* Make sure the descriptor is valid. */
  7.   if (INVALID_NOT_TERMINATED_TD_P (pd))
  8.     /* Not a valid thread handle. */
  9.     return ESRCH;
  10.   int result = 0;
  11.   /* Mark the thread as detached. */
  12.   if (atomic_compare_and_exchange_bool_acq (&pd->joinid, pd, NULL))
  13.     { 
  14.       /* There are two possibilities here. First, the thread might
  15.      already be detached. In this case we return EINVAL.
  16.      Otherwise there might already be a waiter. The standard does
  17.      not mention what happens in this case. */
  18.       if (IS_DETACHED (pd))
  19.     result = EINVAL;
  20.     } 
  21.   else
  22.     /* Check whether the thread terminated meanwhile. In this case we
  23.        will just free the TCB. */
  24.     if ((pd->cancelhandling & EXITING_BITMASK) != 0)
  25.       /* Note that the code in __free_tcb makes sure each thread
  26.      control block is freed only once. */
  27.       __free_tcb (pd);//经过一系列的容错判断,直接释放tcb占用的内存
  28.   return result;
  29. }
最后,我们看一下pthread_join(nptl/pthread_join.c)做了什么

点击(此处)折叠或打开

  1. int
  2. pthread_join (threadid, thread_return)
  3.      pthread_t threadid;
  4.      void **thread_return;
  5. {
  6.   struct pthread *pd = (struct pthread *) threadid;
  7.   /* Make sure the descriptor is valid. */
  8.   if (INVALID_NOT_TERMINATED_TD_P (pd))
  9.     /* Not a valid thread handle. */
  10.     return ESRCH;
  11.   /* Is the thread joinable?. */
  12.   if (IS_DETACHED (pd))
  13.     /* We cannot wait for the thread. */
  14.     return EINVAL;
  15.   struct pthread *self = THREAD_SELF;
  16.   int result = 0;
  17.   /* During the wait we change to asynchronous cancellation. If we
  18.      are canceled the thread we are waiting for must be marked as
  19.      un-wait-ed for again. */
  20.   pthread_cleanup_push (cleanup, &pd->joinid);
  21.   /* Switch to asynchronous cancellation. */
  22.   int oldtype = CANCEL_ASYNC ();
  23.   if ((pd == self
  24.        || (self->joinid == pd
  25.      && (pd->cancelhandling
  26.      & (CANCELING_BITMASK | CANCELED_BITMASK | EXITING_BITMASK
  27.          | TERMINATED_BITMASK)) == 0))
  28.       && !CANCEL_ENABLED_AND_CANCELED (self->cancelhandling))
  29.     /* This is a deadlock situation. The threads are waiting for each
  30.        other to finish. Note that this is a "may" error. To be 100%
  31.        sure we catch this error we would have to lock the data
  32.        structures but it is not necessary. In the unlikely case that
  33.        two threads are really caught in this situation they will
  34.        deadlock. It is the programmer's problem to figure this
  35.        out. */
  36.     result = EDEADLK;
  37.   /* Wait for the thread to finish. If it is already locked something
  38.      is wrong. There can only be one waiter. */
  39.   else if (__builtin_expect (atomic_compare_and_exchange_bool_acq (&pd->joinid,
  40.                                  self,
  41.                                  NULL), 0))
  42.     /* There is already somebody waiting for the thread. */
  43.     result = EINVAL;
  44.   else
  45.     /* Wait for the child. */
  46.     lll_wait_tid (pd->tid);
  47.   /* Restore cancellation mode. */
  48.   CANCEL_RESET (oldtype);
  49.   /* Remove the handler. */
  50.   pthread_cleanup_pop (0);
  51.   if (__builtin_expect (result == 0, 1))
  52.     {
  53.       /* We mark the thread as terminated and as joined. */
  54.       pd->tid = -1;
  55.       /* Store the return value if the caller is interested. */
  56.       if (thread_return != NULL)
  57.     *thread_return = pd->result;//设置返回值
  58.  
  59.  
  60.       /* Free the TCB. */
  61.       __free_tcb (pd);/释放TCB占用内存
  62.     }
  63.   return result;
  64. }
综上,如果要保证创建线程之后,确保无内存泄漏,必须采用如下方法来规范pthread_create的使用:
方法一、创建detached的线程

点击(此处)折叠或打开

  1. void run() { 
  2.     return;
  3.                                                                                                        
  4. int main(){ 
  5.     pthread_t thread; 
  6.     pthread_attr_t attr; 
  7.     pthread_attr_init( &attr ); 
  8.     pthread_attr_setdetachstate(&attr,1); 
  9.     pthread_create(&thread, &attr, run, 0); 
  10.           
  11.     //...... 
  12.     return 0; 
  13. }
方法二、要么线程线程的start_routine结束之前detached

点击(此处)折叠或打开

  1. void run() { 
  2.     pthread_detach(pthread_self()); 
  3.                                                                                                        
  4. int main(){ 
  5.     pthread_t thread;  
  6.     pthread_create(&thread, NULL, run, 0); 
  7.               
  8.     //...... 
  9.     return 0; 
  10. }
方法三、主线程使用pthread_join

点击(此处)折叠或打开

  1. void run() { 
  2.     return;
  3.                                                                                                        
  4. int main(){ 
  5.     pthread_t thread; 
  6.     pthread_create(&thread, NULL, run, 0);  
  7.                                        
  8.     //...... 
  9.     pthread_join(thread,NULL);
  10.     return 0; 
  11. }

解决使用pthread_create函数造成的内存泄露

首先来看一段程序: //test1.cc #include #include #include #include using namespace std; const int MAX_THR...

linux创建线程失败的原因是线程栈大小的设置

线程应用程序最常见导致创建线程失败的原因是线程栈大小的设置。创建一个新的线程,默认情况下系统为线程栈预留了2MB的寻址空间。线程栈起始于进程虚拟 内存的高端地址,并向虚拟内存底端地址方向扩展。取决于线...

pthread_create线程创建的过程剖析

概述 在Linux环境下,pthread库提供的pthread_create()API函数,用于创建一个线程。线程创建失败时,它可能会返回ENOMEM或EAGAIN。这篇文章主要讨论线程创建过程...

VMware几个实用技巧-VM快照管理,扩大硬盘容量,释放硬盘空间

1.VM快照管理 这个功能实在太常用,不用我多废话。这里只是提醒一下还没有用过快照的同学,赶紧的给自己的VM保存点快照吧,这样VM里的系统出了问题或是有其它需要很容易让你还原到原来的某个点,这功能可...

vmware磁盘空间占用越来越大

背景 vmware虚拟机安装操作系统,在使用过程中,不知什么原因,磁盘占用空间越来越大。 原因 虚拟机在使用过程中,需要安装的软件我都是通过拖拽上传的安装包。由于虚拟机会缓存拖拽文件,不自动删除...

pthread_create创建线程后必须使用join或detach释放线程资源[内存泄漏]

原文:http://wifihack.net/blog/2009/12/pthread-create-join-detach-release-thread/这两天在看Pthread 资料的时候,无意中...
  • wag2765
  • wag2765
  • 2016年01月25日 16:43
  • 187

pthread_create 内存泄漏 valgrind

pthread_create()在使用的过程中遇到的一个问题:       今天在使用线程的时候出现了一个问题,利用valgrind监测程序的运行时出现了memory leak。感觉到很奇怪,这样...

隐蔽的内存泄漏——pthread_create 属性设置不当导致

最近解决了一个隐蔽的内存泄漏问题,我们的进程是HA模式,用户不停的切换,会导致内存不停的增长,切换一次,再切回来内存便增加8M左右。原因就是是pthread_create后的僵死线程没有释放导致的内存...
  • abcd1f2
  • abcd1f2
  • 2015年03月25日 12:17
  • 474

解决了一个隐蔽的内存泄漏——pthread_create后没有detach导致内存持续增长

昨天解决了一个隐蔽的内存泄漏问题,原因是pthread_create后的僵死线程没有释放导致的内存持续增长。 现象是这样的:短时间内程序运行正常,但跑了12小时左右,用top查看其内存占用居然高达2...

使用AsyncTask防止Memory Leaks(内存泄漏)的正确姿势

1:合理的使用context–比如我们常使用的Toast,Service,最好使用getApplicationContext(),因为这些在activity结束的时候可能仍在运行 下图展示了一些场景...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:正确使用pthread_create,防止内存泄漏
举报原因:
原因补充:

(最多只允许输入30个字)