线程

线程的退出包括线程主动退出和被动退出,主动退出即线程内调用return或者exit,如果线程设置了detached属性,自动退出时系统是可以回收部分资源(啥资源?),如果线程是joinable,则系统不会回收这部分资源(啥资源?),除非调用pthread_join。(主动退出有两种方法:1.线程不需要一直循环执行,执行结束后就return或者exit。2.线程需要一直循环执行,这时可以使用全局变量标志位,执行完一次后检验标志位,不符合则return或者exit。标志位在主线程或者其他线程中控制)

 

被动退出,即其他线程或者主线程调用pthread_cancel或者pthread_kill(kill是我猜的),将线程取消,这时要格外关系线程资源的释放(锁,信号量),方法见下文。不管是主动退出还是被动退出,都需要主动free掉malloc的内存。

 

Linux线程退出、资源回收、资源清理的方法

首先说明线程中要回收哪些资源,理解清楚了这点之后在思考资源回收的问题。

1、子线程创建时从父线程copy出来的栈内存;

  线程退出有多种方式,如return,pthread_exit,pthread_cancel等;线程分为可结合的(joinable)和 分离的(detached)两种,如果没有在创建线程时设置线程的属性为PTHREAD_CREATE_DETACHED,则线程默认是可结合的

可结合的线程在线程退出后不会立即释放资源,必须要调用pthread_join来显式的结束线程。

分离的线程在线程退出时系统会自动回收资源。

  对于这类资源,主要通过 【设置分离属性 】和 【 pthread_join()】 两种方法来处理。

  其中设置分离属性又可以分别用【pthread_attr_setdetachstat()】和【pthread_detach()】来处理。

2、子线程内部单独申请的堆内存(malloc、realloc、calloc)和锁资源mutex

  一旦又处于挂起状态的取消请求(即加锁之后,解锁之前),线程在执行到取消点时如果只是草草收场,这会将共享变量以及pthreads对象(例如互斥量)置于一种不一致状态,可能导致进程中其他线程产生错误结果、死锁,甚至造成程序崩溃。为避免这一问题:

  使用清理函数pthread_cleanup_push()和pthread_cleanup_pop()来处理。

 

 

 

线程退出和资源回收

线程退出有多种方式,如return,pthread_exit,pthread_cancel等;线程分为可结合的(joinable)和 分离的(detached)两种,如果没有在创建线程时设置线程的属性为PTHREAD_CREATE_DETACHED,则线程默认是可结合的。可结合的线程在线程退出后不会立即释放资源,必须要调用pthread_join来显式的结束线程。分离的线程在线程退出时系统会自动回收资源。

一、设置分离线程的几种方法:

1.在创建线程时加上

pthread_attr_t attr;
pthread_t thread;
pthread_attr_init (&attr);

/* 设置线程的属性为分离的 */

pthread_attr_setdetachstat(&attr, PTHREAD_CREATE_DETACHED);
pthread_create (&thread, &attr, &thread_function, NULL);

/*hy******************************线程其他属性的设置*********************************

     struct sched_param param;

     pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
     param.sched_priority = 80;
     pthread_attr_setschedparam(&attr,&param);
     pthread_attr_setstacksize(&attr,512*1024);

     pthread_create(&a_thread[seq],&attr,cmd_msg,(void*)seq);

*********************************************************************************hy*/

/* 销毁一个目标结构,并且使它在重新初始化之前不能重新使用 */

pthread_attr_destroy (&attr);

2.在线程中调用pthread_detach(pthread_self());

3.主线程中调用pthread_detach(pid),pid为子线程的线程号。

要注意的是,设置为分离的线程是不能调用pthread_join的,调用后会出错
 

 

二、可结合的线程的几种退出方式

1. 子线程使用return退出,主线程中使用pthread_join回收线程

2.子线程使用pthread_exit退出,主线程中使用pthread_join接收pthread_exit的返回值,并回收线程

一个线程的结束,有两种方式,一种是正常结束return。一种是使用pthread_exit。对于使用pthread_exit结束的线程,可以返回一个"status"给主线程。这个参数就和pthread_join第二个参数就对应上了。pthread_exit返回的是一种状态,一般是一个数字,这样我们在pthread_join中就可以获得这个数字。

3.主线程中调用pthread_cancel,然后调用pthread_join回收线程

注意:pthread_cancel函数执行的条件:,

在要杀死的子线程对应的处理函数的内部

      1、产生了系统调用(sleep、read、write、open等系统接口)

      2、pthread_testcancel();//设置取消点

线程属性结构如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

typedef struct

{

       int    detachstate;           //线程的分离状态

       int    schedpolicy;            // 线程调度策略

       structsched_param      schedparam;    //线程的调度参数

       int    inheritsched;          //线程的继承性

       int    scope;                 //线程的作用域

       size_t     guardsize;         //线程栈末尾的警戒缓冲区大小

       int    stackaddr_set;

       void*    stackaddr;           //线程栈的位置

       size_t     stacksize;          //线程栈的大小

}pthread_attr_t;

   

 

pthread_create 创建线程时,若不指定分配堆栈大小,系统会分配默认值,查看默认值方法如下:

# ulimit -s
8192
#

上述表示为8M;单位为KB。

也可以通过# ulimit -a 其中 stack size 项也表示堆栈大小。ulimit -s  value 用来重新设置stack 大小。

一般来说 默认堆栈大小为 8388608; 堆栈最小为 16384 。 单位为字节。

堆栈最小值定义为 PTHREAD_STACK_MIN ,包含#include <limits.h>后可以通过打印其值查看。对于默认值可以通过pthread_attr_getstacksize (&attr, &stack_size); 打印stack_size来查看。

尤其在嵌入式中内存不是很大,若采用默认值的话,会导致出现问题,若内存不足,则 pthread_create 会返回 12,定义如下:

#define EAGAIN 11

#define ENOMEM 12 /* Out of memory */

上面了解了堆栈大小,下面就来了解如何使用 pthread_attr_setstacksize 重新设置堆栈大小。先看下它的原型:

#include <pthread.h>
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

attr 是线程属性变量;stacksize 则是设置的堆栈大小。 返回值0,-1分别表示成功与失败。

这里是使用方法

  ***************************

系统可生成最大线程数
cat /proc/sys/kernel/threads-max

******************************

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

pthread_t thread_id;

 

  int ret ,stacksize = 20480; /*thread 堆栈设置为20K,stacksize以字节为单位。*/

  pthread_attr_t attr;

  ret = pthread_attr_init(&attr); /*初始化线程属性*/<br>

  if (ret != 0)return -1;

  ret = pthread_attr_setstacksize(&attr, stacksize);

 

  if(ret != 0)return -1;

  ret = pthread_create (&thread_id, &attr, &func, NULL);

 

  if(ret != 0)return -1;

  ret = pthread_attr_destroy(&attr); /*不再使用线程属性,将其销毁*/

 

  if(ret != 0)return -1;

   在写网络服务器程序时可能需要实现多线程接收多个客户端的数据,我实现方式比较傻,死循环等待client的connect,connect之后创建thread,这样其实有一个问题,服务器程序需要长期运行,长时间线程的创建,线程资源的回收就是一个问题。

Linux系统中程序的线程资源是有限的,表现为对于一个程序其能同时运行的线程数是有限的。而默认的条件下,一个线程结束后,其对应的资源不会被释放,于是,如果在一个程序中,反复建立线程,而线程又默认的退出,则最终线程资源耗尽,进程将不再能建立新的线程。

解决这个问题,有2种方式,系统自动释放线程资源,或者由另一个线程释放该线程资源。

进程运行后,本身,也是一个线程,主线程,主线程和主线程建立的线程共享进程资源。不同于其他线程,在于主线程运行结束后,程序退出,所有程序建立的线程也会退出。

一 系统自动释放
如果想在线程结束时,由系统释放线程资源,则需要设置线程属性为detach,是线程分离主线程

代码上,可以这样表示:

pthread_t t;
pthread_attr_t a; //线程属性
pthread_attr_init(&a);  //初始化线程属性
pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED);      //设置线程属性
pthread_create( &t, &a, GetAndSaveAuthviewSDRStub, (void*)lp);                   //建立线程

二 由另一个线程将该资源释放

代码上,可以这样表示:

pthread_t t;
pthread_create( NULL, NULL, GetAndSaveAuthviewSDRStub, (void*)lp);
pthread_join( t);

pthread_join( t)等待线程t退出,并释放t线程所占用的资源。

pthread_join函数会阻塞等待指定线程退出,然后回收资源,这样就有同步的功能,使一个线程等待另一个线程退出,然后才继续运行,但是对于服务器程序如果主线程在新创建的线程工作时还需要做别的事情,这种方法不是很好,就需要使用方法一

 

 linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。
若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。

unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为joinable,然后适时调用pthread_join.

 

还有2个函数可以实现线程的分离,pthread_detach(threadid)和pthread_detach(pthread_self())。

这2个函数区别是调用他们的线程不同,没其他区别。

pthread_detach(threadid)函数的功能是使线程ID为threadid的线程处于分离状态,一旦线程处于分离状态,该线程终止时底层资源立即被回收;否则终止子线程的状态会一直保存(占用系统资源)直到主线程调用pthread_join(threadid,NULL)获取线程的退出状态。
通常是主线程使用pthread_create()创建子线程以后,一般可以调用pthread_detach(threadid)分离刚刚创建的子线程,这里的threadid是指子线程的threadid;如此以来,该子线程止时底层资源立即被回收;
被创建的子线程也可以自己分离自己,子线程调用pthread_detach(pthread_self())就是分离自己,因为pthread_self()这个函数返回的就是自己本身的线程ID。

 

资源清理

一旦有处于挂起状态的取消请求(即加锁之后,解锁之前),线程在执行到取消点时如果只是草草收场,这会将共享变量以及pthreads对象(例如互斥量)置于一种不一致状态,可能导致进程中其他线程产生错误结果、死锁,甚至造成程序崩溃。为避免这一问题:

  使用清理函数pthread_cleanup_push()和pthread_cleanup_pop()来处理,这两个函数必需成对出现,不然会编译错误。

不论是可预见的线程种植还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证种植时能顺利的释放掉自己所占用的资源,包括单独申请的堆内存,特别是锁资源,就是一个必需考虑的问题。

最近常出现的情形是资源独占锁的使用:线程为了访问临界共享资源而为其加上锁,但在访问过程被外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可见的,因此的确需要一个机制来简化用于资源释放的编程。

在POSIX线程API中提供了一个pthread_clean_push()/pthread_cleanup_pop()函数对,用于自动释放资源----从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。

 

API定义如下:

void pthread_cleanup_push(void (*routine) (void  *),  void *arg)
void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,

void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。

有三种情况线程清理函数会被调用:

  • 线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
  • 线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
  • 线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.

注意:如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,是不会执行清理函数的。

void routine()函数可参照如下定义:

1

2

3

4

5

void *cleanup(void* p)

{

        free(p);

        printf("清理函数\n");

}

 线程主动清理过程的严谨写法:

1

2

3

4

5

6

7

8

9

void thread_fun(void*p)

{<br>     p=malloc(20);

        pthread_cleanup_push(cleanup,p);

        printf("子线程\n");

        sleep(1);            //系统调用,用来响应pthread_cancel函数

        printf("是否杀死了线程\n");  //如果线程在上一句被杀死,这一句不会被打印

        pthread_exit(NULL);      //不管线程是否被杀死,这一句都会检测清理函数,并执行

        pthread_clean_pop(1);

}

  注意:在子线程中如果申请了单独的堆空间,不应用free直接清理;因为假如在线程中直接free,如果,在free之后线程被取消,清理函数被执行,则会出现重复free的情况。

  情况如下:

void thread_fun(void*p)

{

     p=malloc(20);

        pthread_cleanup_push(cleanup,p);

        printf("子线程\n");

        sleep(1);            //系统调用,用来响应pthread_cancel函数

        printf("是否杀死了线程\n");  //如果线程在上一句被杀死,这一句不会被打印

        free(p);      //不管线程是否被杀死,这一句都会检测清理函数,并执行

       //假如函数在此处被cancel ,可能会出现重复free   

          sleep(1);//在此处系统调用,响应pthread_cancel(),会执行清理函数   

          return NULL;   

          pthread_clean_pop(1);//由于上一句return,所以这一句不执行,即清理函数不会执行

}

  

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:

#define pthread_cleanup_push(routine,arg)                                    

{

         struct _pthread_cleanup_buffer _buffer;                                  

        _pthread_cleanup_push (&_buffer, (routine), (arg));

#define pthread_cleanup_pop(execute)                                         

        _pthread_cleanup_pop (&_buffer, (execute));

}

  

可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。


以下是使用方法:

pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);

pthread_mutex_lock(&mut);

.......

pthread_mutex_unlock(&mut);

pthread_cleanup_pop(0);

  

必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在
pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的
mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的
Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下
代码段相当:

{

        int oldtype;

        pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

        pthread_cleanup_push(routine, arg);

         ......

        pthread_cleanup_pop(execute);

        pthread_setcanceltype(oldtype, NULL);

 }   

补充:
在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。
/****hy***/
pthread_t ddr_tid;

if(pthread_create(&ddr_tid, NULL, (void*)(ComplexSdram_parallel), NULL)!=0)
                perror("pthread_create() error"); 
.......
.......
.......
case StopDDRTest:
        {
            printf("\nStop ddr test thread!");
            if (pthread_cancel(ddr_tid) != 0) 
            {                                              
                perror("pthread_cancel() error");   
                                                                                   
               } 
           if (pthread_join(ddr_tid, &mystatus) != 0) 
               {  
                  if(mystatus != PTHREAD_CANCELED)
                   {                                      
                   perror("pthread_join() error");                                             
              
                 }                                                                    
               }
            if(mystatus == PTHREAD_CANCELED)
            { 
                puts("PTHREAD_CANCELED");
                free(buf);
                DDRCNT=0;
                tResultStruct->ucTstResult=0x55;
                break;
            }

            tResultStruct->ucTstResult=0xAA;
            break;
            
        }
------------------------------------------------------------------------------------------------------------------

Linux线程退出方式总结

原文链接:https://blog.csdn.net/keheinash/article/details/50682435


在编写多线程代码时,经常面临线程安全退出的问题。 
一般情况下,选择检查标志位的方式: 
在线程的while循环中,执行完例程后,都对标志位进行检查,如果标志位指示继续执行则再次执行例程,如果标志位设置为退出状态,则跳出循环,结束线程的运行。


void staledtest(void)
{
	iLedState[0]=0;
	iLedState[1]=1;
	iLedState[2]=1;
	while(1)
    {  
        if(iLedState[0] == 0)
        {
            Mgpu_MBLed_On(0);//STA
			Mgpu_MBLed_On(1);
			sleep(1);
			Mgpu_MBLed_Off(0);
			Mgpu_MBLed_On(1);
			sleep(1);
			Mgpu_MBLed_Off(0);
			Mgpu_MBLed_On(0);
			sleep(1);
        }
		else
        {
            Mgpu_MBLed_Off(0);
            iLedState[0] = 2;
            return;
        }
    }
}
main()
{
.....
.....
.....
		if((pthread_create(&pntidb,NULL,(void *)stbledtest,NULL))!=0) 
            { 
                printf("stbledtest Create thread error!\n"); 
                exit(1); 
            } 
.....
.....
iLedState[0] =2;
}

标志位如iLedState[0]。

这个标志位需要主线程(或其他线程)设置,设置后,主线程调用pthread_join接口进入休眠(接口参数指定了等待的线程控制指针),子线程退出后,主线程会接收到系统的信号,从休眠中恢复,这个时候就可以去做相关的资源清除动作。

这个方法可以保证子线程完全退出,主线程再去做相关的资源清除操作

时序图如下 


但是某些应用中,或许会发生下面情况: 
子线程阻塞在某个操作无法被唤醒,即使主线程设置了标志位,由于子线程进入了休眠无法醒过来,也没有办法去检查标志位,这个时候调用pthread_join进入休眠的主线程等待不到子线程退出的信号,也会一直休眠,系统进入死锁。

为了更安全地使线程退出,主线程通过pthread_cancel函数来请求取消同一进程中的其他线程,再调用pthread_join等待指定线程退出。使用pthread_cancel接口,需要了解Linux下线程的两个属性,可取消状态和可取消类型,以及取消点的概念。

可取消状态:包括PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE。当线程处于PTHREAD_CANCEL_ENABLE,收到cancel请求会使该线程退出运行;反之,若处于PTHREAD_CANCEL_DISABLE,收到的cancel请求将处于未决状态,线程不会退出。线程启动时的默认可取消状态为PTHREAD_CANCEL_ENABLE,可以通过接口pthread_setcancelstate改变可取消状态的属性。

可取消类型:包括PTHREAD_CANCEL_DEFERRED和PTHREAD_CANCEL_ASYNCHRONOUS。当处于PTHREAD_CANCEL_DEFERRED,线程在收到cancel请求后,需要运行到取消点才能退出运行如果处于PTHREAD_CANCEL_ASYNCHRONOUS,可以在任意时间取消,只要收到cancel请求即可马上退出。线程启动时默认可取消类型为PTHREAD_CANCEL_DEFERRED,可通过pthread_setcanceltype修改可取消类型。

取消点:线程检查是否被取消并按照请求进行动作的一个位置。

采用PTHREAD_CANCEL_DEFERRED取消方式是因为线程可能在获取临界资源后(如获取锁),未释放资源前收到退出信号,如果使用PTHREAD_CANCEL_ ASYNCHRONOUS的方式,无论线程运行到哪个位置,都会马上退出,而占有的资源却得不到释放。 
采用PTHREAD_CANCEL_DEFERRED取消方式,线程需要运行到取消点才退出,而主线程在调用pthread_cancel后,不能马上进行线程资源释放,必须调用pthread_join进入休眠,直至等待指定线程退出。

使用PTHREAD_CANCEL_DEFERRED方式并不能完全避免这个问题,因为无法保证在获取临界资源后(比如lock操作)不会进行可以作为取消点的操作(如进行sleep),此时主线程如果对该线程发送cancel信号,线程将会在不释放锁的情况下直接结束运行,即还是会出现在释放资源前线程就退出的问题。 
为了避免上述情况,不仅需要设置可取消类型,还需要设置可取消状态。将获取临界资源-释放临界资源之间的代码块都设置成PTHREAD_CANCEL_DISABLE状态,其余的代码块都设置成PTHREAD_CANCEL_ENABLE状态,确保线程在安全的地方退出。如果在可以安全退出的代码块不存在取消点系统调用,可以调用pthread_testcancel函数自己添加取消点。


伪代码描述如下:
 

void* subThread(void*)
{
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldCancleState);
    …;//不存在获取临界资源操作,可以安全退出的代码块
    pthread_testcancel();//如果可以安全退出的代码块不存在取消点操作,可以自己添加pthread_testcancel调用,线程执行到这个调用就会退出
    /*还有一种方法,在可以安全退出的代码块,我们将线程的可取消类型设置成PTHREAD_CANCEL_ ASYNCHRONOUS,这样即使没有取消点也可以马上退出*/

    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldCancleState);
    /*存在获取-释放临界资源操作,如果在lock和unlock之间的运行收到cancel信号,且可取消状态为enable,则锁永远无法被释放*/
    Lock();
    …;
    Unlock();
}

void* mainThread(void*)
{
    pthread_cancel(subThread);//给subThread发送退出信号
    pthread_join(subThread,null);//进入休眠,直到subThread退出成功
}


无论使用哪种方式,核心点就是要保证线程退出的时候不会获取了某些临界资源而无法释放

POSIX.1定义的取消点见下: 
这里写图片描述
这里写图片描述

注意:当主线程调用pthread_cancel接口后,只是将取消请求发送给指定线程, 
对接口的成功调用不能保证指定线程已经退出,需要调用pthread_join等待指定线程完全退出,再进行相关资源的释放。
————————————————
版权声明:本文为CSDN博主「铁桶小分队」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/keheinash/article/details/50682435

------------------------------------------------------------------------------------------------------------------------------------------------------------------------

如何正确的终止正在运行的子线程

  最近开发一些东西,线程数非常之多,当用户输入Ctrl+C的情形下,默认的信号处理会把程序退出,这时有可能会有很多线程的资源没有得到很好的释放,造成了内存泄露等等诸如此类的问题,本文就是围绕着这么一个使用场景讨论如何正确的终止正在运行的子线程。其实本文更确切的说是解决如何从待终止线程外部安全的终止正在运行的线程

首先我们来看一下,让当前正在运行的子线程停止的所有方法

1.任何一个线程调用exit

2.pthread_exit

3.pthread_kill

4.pthread_cancel 

 

下面我们一一分析各种终止正在运行的程序的方法

 

任何一个线程调用exit

任何一个线程只要调用了exit都会导致进程结束,各种子线程当然也能很好的结束了,可是这种退出会有一个资源释放的问题.我们知道当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。没错,标准C++ IO流也会很好的在exit退出时得到flush并且释放资源,这些东西并不会造成资源的浪费(系统调用main函数入口类似于exit(main(argc,argv))).表面上似乎所有的问题都能随着进程的结束来得到很好的处理,其实并不然,我们程序从堆上分配的内存就不能得到很好的释放,如new ,delete后的存储空间,这些空间进程结束并不会帮你把这部分内存归还给内存.(本文初稿时,因基础不牢固,此处写错,事实上无论进程这样结束,系统都将会释放掉所有代码所申请的资源,无论是堆上的还是栈上的。(感谢ZKey的指导)。这种结束所有线程(包括主线程)的方式实际上在很多时候是非常可取的,但是对于针对关闭时进行一些别的逻辑的处理(指非资源释放逻辑)就不会很好,例如我想在程序被kill掉之前统计一下完成了多少的工作,这个统计类似于MapReduce,需要去每个线程获取,并且最后归并程一个统一的结果等等场景)

(hy:进程结束时,各种资源包括malloc的堆,大部分操作系统会自动回收;而线程结束时,其资源是不会自动释放的(除非设置属性为detach)!)

 

pthread_exit

此函数的使用场景是当前运行的线程运行pthread_exit得到退出,对于各个子线程能够清楚地知道自己在什么时候结束的情景下,非常好用,可是实际上往往很多时候一个线程不能知道知道在什么时候该结束,例如遭遇Ctrl+C时,kill进程时,当然如果排除所有的外界干扰的话,那就让每个线程干完自己的事情后,然后自觉地乖乖的调用pthread_exit就可以了,这并不是本文需要讨论的内容,本文的情景就是讨论如何处理特殊情况。

这里还有一种方法,既然子线程可以通过pthread_exit来正确退出,那么我们可以在遭遇Ctrl+C时,kill进程时处理signal信号,然后分别给在某一个线程可以访问的公共区域存上一个flag变量,线程内部每运行一段时间(很短)来检查一下flag,若发现需要终止自己时,自己调用pthread_exit,此法有一个弱点就是当子线程需要进行阻塞的操作时,可能无暇顾及检查flag,例如socket阻塞操作。如果你的子线程的任务基本没有非阻塞的函数,那么这么干也不失为一种很好的方案。

 

pthread_kill

不要被这个可怕的邪恶的名字所吓倒,其实pthread_kill并不像他的名字那样威力大,使用之后,你会感觉,他徒有虚名而已

pthread_kill的职责其实只是向指定的线程发送signal信号而已,并没有真正的kill掉一个线程,当然这里需要说明一下,有些信号的默认行为就是exit,那此时你使用pthread_kill发送信号给目标线程,目标线程会根据这个信号的默认行为进行操作,有可能是exit。当然我们同时也可以更改获取某个信号的行为,以此来达到我们终止子线程的目的。

复制代码

 1 #define _MULTI_THREADED
 2 #include <pthread.h>
 3 #include <stdio.h>
 4 #include <signal.h>
 5 #include "check.h"
 6 
 7 #define NUMTHREADS 3
 8 void sighand(int signo);
 9 
10 void *threadfunc(void *parm)
11 {
12   pthread_t             self = pthread_self();
13   pthread_id_np_t       tid;
14   int                   rc;
15 
16   pthread_getunique_np(&self, &tid);
17   printf("Thread 0x%.8x %.8x entered\n", tid);
18   errno = 0;
19   rc = sleep(30);
20   if (rc != 0 && errno == EINTR) {
21     printf("Thread 0x%.8x %.8x got a signal delivered to it\n",
22            tid);
23     return NULL;
24   }
25   printf("Thread 0x%.8x %.8x did not get expected results! rc=%d, errno=%d\n",
26          tid, rc, errno);
27   return NULL;
28 }
29 
30 int main(int argc, char **argv)
31 {
32   int                     rc;
33   int                     i;
34   struct sigaction        actions;
35   pthread_t               threads[NUMTHREADS];
36 
37   printf("Enter Testcase - %s\n", argv[0]);
38  
39   printf("Set up the alarm handler for the process\n");
40   memset(&actions, 0, sizeof(actions));
41   sigemptyset(&actions.sa_mask);
42   actions.sa_flags = 0;
43   actions.sa_handler = sighand;
44 
45   rc = sigaction(SIGALRM,&actions,NULL);
46   checkResults("sigaction\n", rc);
47 
48   for(i=0; i<NUMTHREADS; ++i) {
49     rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
50     checkResults("pthread_create()\n", rc);
51   }
52 
53   sleep(3);
54   for(i=0; i<NUMTHREADS; ++i) {
55     rc = pthread_kill(threads[i], SIGALRM);
56     checkResults("pthread_kill()\n", rc);
57   }
58 
59   for(i=0; i<NUMTHREADS; ++i) {
60     rc = pthread_join(threads[i], NULL);
61     checkResults("pthread_join()\n", rc);
62   }
63   printf("Main completed\n");
64   return 0;
65 }
66 
67 void sighand(int signo)
68 {
69   pthread_t             self = pthread_self();
70   pthread_id_np_t       tid;
71  
72   pthread_getunique_np(&self, &tid);
73   printf("Thread 0x%.8x %.8x in signal handler\n",
74          tid);
75   return;
76 }

复制代码

 

 运行输出为:

复制代码

 1 Output:
 2 
 3 Enter Testcase - QP0WTEST/TPKILL0
 4 Set up the alarm handler for the process
 5 Thread 0x00000000 0000000c entered
 6 Thread 0x00000000 0000000d entered
 7 Thread 0x00000000 0000000e entered
 8 Thread 0x00000000 0000000c in signal handler
 9 Thread 0x00000000 0000000c got a signal delivered to it
10 Thread 0x00000000 0000000d in signal handler
11 Thread 0x00000000 0000000d got a signal delivered to it
12 Thread 0x00000000 0000000e in signal handler
13 Thread 0x00000000 0000000e got a signal delivered to it
14 Main completed

复制代码

  我们可以通过截获的signal信号,来释放掉线程申请的资源,可是遗憾的是我们不能再signal处理里调用pthread_exit来终结掉线程,因为pthread_exit是中介当前线程,而signal被调用的方式可以理解为内核的回调,不是在同一个线程运行的,所以这里只能做处理释放资源的事情,线程内部只有判断有没有被中断(一般是EINTR)来断定是否要求自己结束,判定后可以调用pthread_exit退出。

   此法对于一般的操作也是非常可行的,可是在有的情况下就不是一个比较好的方法了,比如我们有一些线程在处理网络IO事件,假设它是一种一个客户端对应一个服务器线程,阻塞从Socket中读消息的情况。我们一般在网络IO的库里面回家上对EINTR信号的处理,例如recv时发现返回值小于0,检查error后,会进行他对应的操作。有可能他会再recv一次,那就相当于我的线程根本就不回终止,因为网络IO的类有可能不知道在获取EINTR时要终止线程。也就是说这不是一个特别好的可移植方案,如果你线程里的操作使用了很多外来的不太熟悉的类,而且你并不是他对EINTR的处理手段是什么,这是你在使用这样的方法来终止就有可能出问题了。而且如果你不是特别熟悉这方面的话你会很苦恼,“为什么我的测试代码全是ok的,一加入你们部门开发的框架进来就不ok了,肯定是你们框架出问题了”。好了,为了不必要的麻烦,我最后没有使用这个方案。

 

pthread_cancel

  这个方案是我最终采用的方案,我认为是解决这个问题,通用的最好的解决方案,虽然前面其他方案的有些问题他可能也不好解决,但是相比较而言,还是相当不错的

pthread_cancel可以单独使用,因为在很多系统函数里面本身就有很多的断点,当调用这些系统函数时就会命中其内部的断点来结束线程,如下面的代码中,即便注释掉我们自己设置的断点pthread_testcancel()程序还是一样的会被成功的cancel掉,因为printf函数内部有取消点(如果大家想了解更多的函数的取消点情况,可以阅读《Unix高级环境编程》的线程部分)

复制代码

 1 #include <pthread.h>
 2 #include <stdio.h>
 3 #include<stdlib.h>
 4 #include <unistd.h>
 5 void *threadfunc(void *parm)
 6 {
 7   printf("Entered secondary thread\n");
 8   while (1) {
 9     printf("Secondary thread is looping\n");
10     pthread_testcancel();
11     sleep(1);
12   }
13   return NULL;
14 }
15 
16 int main(int argc, char **argv)
17 {
18   pthread_t             thread;
19   int                   rc=0;
20 
21   printf("Entering testcase\n");
22 
23   /* Create a thread using default attributes */
24   printf("Create thread using the NULL attributes\n");
25   rc = pthread_create(&thread, NULL, threadfunc, NULL);
26   checkResults("pthread_create(NULL)\n", rc);
27 
28   /* sleep() is not a very robust way to wait for the thread */
29   sleep(1);
30 
31   printf("Cancel the thread\n");
32   rc = pthread_cancel(thread);
33   checkResults("pthread_cancel()\n", rc);
34 
35   /* sleep() is not a very robust way to wait for the thread */
36   sleep(10);
37   printf("Main completed\n");
38   return 0;
39 }

复制代码

输出:

Entering testcase
Create thread using the NULL attributes
Entered secondary thread
Secondary thread is looping
Cancel the thread
Main completed

    POSIX保证了绝大部分的系统调用函数内部有取消点,我们看到很多在cancel调用的情景下,recv和send函数最后都会设置pthread_testcancel()取消点,其实这不是那么有必要的,那么究竟什么时候该pthread_testcancel()出场呢?《Unix高级环境编程》也说了,当遇到大量的基础计算时(如科学计算),需要自己来设置取消点。

ok,得益于pthread_cancel,我们很轻松的把线程可以cancel掉,可是我们的资源呢?何时释放...

下面来看两个pthread函数

1.void pthread_cleanup_push(void (*routine)(void *), void *arg);  
2.void pthread_cleanup_pop(int execute); 

这两个函数能够保证在 1函数调用之后,2函数调用之前的任何形式的线程结束调用向pthread_cleanup_push注册的回调函数
另外我们还可通过下面这个函数来设置一些状态

 int pthread_setcanceltype(int type, int *oldtype); 

CancelabilityCancelability StateCancelability Type
disabledPTHREAD_CANCEL_DISABLEPTHREAD_CANCEL_DEFERRED
disabledPTHREAD_CANCEL_DISABLEPTHREAD_CANCEL_ASYNCHRONOUS
deferredPTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DEFERRED
asynchronousPTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_ASYNCHRONOUS

  当我们设置type为PTHREAD_CANCEL_ASYNCHRONOUS时,线程并不会等待命中取消点才结束,而是立马结束

  好了,下面贴代码:

复制代码

 1 #include <pthread.h>                                                            
 2 #include <stdio.h>                                                              
 3 #include <stdlib.h>                                                             
 4 #include <unistd.h>                                                             
 5 #include <errno.h>                                                                                
 6 int footprint=0;
 7 char *storage; 
 8 void freerc(void *s)
 9 {
10    free(s);
11    puts("the free called");    
12 }                                                               
13 
14 static void checkResults(char *string, int rc) {
15   if (rc) {
16     printf("Error on : %s, rc=%d",
17            string, rc);
18     exit(EXIT_FAILURE);
19   }
20   return;
21 }
22                                                                                 
23 void *thread(void *arg) {                                                                                                                    
24   int           rc=0, oldState=0; 
25   rc = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState);   //close the cancel switch   
26   checkResults("pthread_setcancelstate()\n", rc);                                                                        
27   if ((storage = (char*) malloc(80)) == NULL) {                                 
28     perror("malloc() failed");                                                  
29     exit(6);                                                                    
30   }                                                                             
31    rc = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&oldState);   //open the cancel  switch                                                                        
32    checkResults("pthread_setcancelstate(2)\n", rc);  
33 /* Plan to release storage even if thread doesn't exit normally */            
34                                                                                 
35   pthread_cleanup_push(freerc, storage);   /*the free is method here   you can use your own method here*/                                       
36                                                                                 
37   puts("thread has obtained storage and is waiting to be cancelled");           
38   footprint++;                                                                  
39   while (1)
40     {      
41          pthread_testcancel();   //make a break point here     
42            //pthread_exit(NULL);    //test exit to exam whether the freerc method called                                                     
43             sleep(1);  
44     }                                                                 
45                                                                                 
46   pthread_cleanup_pop(1);                                                       
47 }                                                                               
48                                                                                 
49 main() {                                                                        
50   pthread_t thid; 
51  void                 *status=NULL;                                                              
52                                                                                 
53   if (pthread_create(&thid, NULL, thread, NULL) != 0) {                         
54     perror("pthread_create() error");                                           
55     exit(1);                                                                    
56   }                                                                             
57                                                                                 
58   while (footprint == 0)                                                        
59     sleep(1);                                                                   
60                                                                                 
61   puts("IPT is cancelling thread");                                             
62                                                                                 
63   if (pthread_cancel(thid) != 0) {                                              
64     perror("pthread_cancel() error");   
65     sleep(2);                                        
66     exit(3);                                                                    
67   }                                                                             
68                                                                                 
69   if (pthread_join(thid, &status) != 0) {  
70     if(status != PTHREAD_CANCELED){                                      
71         perror("pthread_join() error");                                             
72         exit(4);
73     }                                                                    
74   }
75  if(status == PTHREAD_CANCELED)
76     puts("PTHREAD_CANCELED");
77      
78  puts("main exit");                                                                        
79 } 

复制代码

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

当进程退出后,动态申请的内存会自动释放吗

(hy:进程退出时,对于大部分操作系统,动态申请的内存会自动释放。而进程中某一线程退出时,动态申请的内存不会自动回收,需要手动free,因为线程是共享进程资源的,只有当该进程退出时,线程的资源才会释放)

stackoverflow有人问了这么一个问题,下面这段程序执行完毕后,malloc的内存会释放吗

int main () {
  int *p = malloc(10 * sizeof *p);
  *p = 42;
  return 0;  //Exiting without freeing the allocated memory
}
赞数最多的这么回答:

It depends on the operating system. The majority of modern (and all major) operating systems will free memory not freed by the program when it ends.

Relying on this is bad practice and it is better to free it explicitly. The issue isn't just that your code looks bad. You may decide you want to integrate your small program into a larger, long running one. Then a while later you have to spend hours tracking down memory leaks.
Relying on a feature of an operating system also makes the code less portable.

因此这些内存是会被大部分现代操作系统释放掉的,这些系统包括
MacOS X, Linux, all recent version of Windows, and all currently manufactured phone handsets

一些老的系统不会释放:

If you're programming on microcontrollers, on MacOS 9 or earler, DOS, or Windows 3.x, then you might need to be concerned about memory leaks making memory permenantly unavailable to the whole operating system.

解释如下:

Most modern operating systems employ a memory manager, and all userland processes only see so-called virtual memory, which is not related to actual system memory in a way that the program could inspect. This means that programs cannot simply read another process's memory or kernel memory. It also means that the memory manager will completely "free" all memory that has been assigned to a process when that process terminates, so that memory leaks within the program do not usually "affect" the rest of the system (other than perhaps forcing a huge amount of disk swapping and perhaps some "out of memory" behaviour).

This doesn't mean that it's in any way OK to treat memory leaks light-heartedly, it only means that no single program can casually corrupt other processes on modern multi-tasking operating systems (deliberate abuse of administrative privileges notwithstanding, of course).

此外The Linux Programming Interface书中有这么一段:

When a process terminates, all of its memory is returned to the system, including heap memory allocated by functions in the malloc package. In programs that allocate memory and continue using it until program termination, it is common to omit calls to free(), relying on this behavior to automatically free the memory.  This can be especially useful in programs that allocate many blocks of memory, since adding multiple calls to free() could be expensive in terms of CPU time, as well as perhaps being complicated to code. 
————————————————
版权声明:本文为CSDN博主「coolibin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/libinjlu/article/details/54865887

------------------------------------------------------------------------------------------------------------------

线程创建失败的常见原因

线程应用程序最常见导致创建线程失败的原因是线程栈大小的设置。

创建一个新的线程,默认情况下系统为线程栈预留了2MB的寻址空间。

线程栈起始于进程虚拟内存的高端地址,并向虚拟内存底端地址方向扩展。

取决于线程本身的大小以及其它线程内存分配的情况,进程虚拟地址空间消耗过快可能导致创建线程失败。

这里有一个测试程序可以看到,Linux下最多可以创建多少个线程。

#include <pthread.h>

#include <stdio.h>

#include <string.h>

 

void *ThreadFunc()

{

    static int count = 1;

    printf ("Create thread %d\n", count);

    pthread_detach (pthread_self());

    count++;

}

 

main(void)

{

    int     err;

    pthread_t tid;

    while (1){

           err = pthread_create(&tid, NULL, ThreadFunc, NULL);

           if (err != 0){

               printf("can't create thread: %s\n", strerror(err));

           break;

           }  

    }

}

 

输出结果如下:

Create thread 301

Create thread 302

can't create thread: Cannot allocate memory

用 ulimit -s 可以查看到栈的默认大小为10240K

32位linux下的进程用户空间是3072M, 3072/10.24=300。为什么实际会比计算出来的多2个,这个原因还不太清楚。

可以在调用 pthread_create 的时候用 pthread_attr_getstacksize 设置栈的大小,或者直接用 ulimit -s 设置栈的大小。

 

这里用到了pthread_detach (pthread_self())来释放线程所占用的内存资源(线程内核对象和线程堆栈)。这样就可以创建更多的线程,而不会出现Cannot allocate memory的错误。

 

linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。
若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。

unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值