• 线程的while循环,执行完例程后,都对标志位进行检查,如果标志位指示继续执行则再次执行例程,如果标志位设置为退出状态,则跳出循环,结束线程运行。这个标志位需要主线程(或其他线程)设置,设置后,主线...

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

    这个标志位需要主线程(或其他线程)设置,设置后,主线程调用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等待指定线程完全退出,再进行相关资源的释放。

    展开全文
  • 线程退出 新创建的线程从执行用户定义的函数处开始执行,直到出现以下情况时退出: 调用pthread_exit函数退出。调用pthread_cancel函数取消该线程。创建线程的进程退出或者整个函数结束。其中的一个线程执行了...

    线程退出

    新创建的线程从执行用户定义的函数处开始执行,直到出现以下情况时退出:

    • 调用pthread_exit函数退出。
    • 调用pthread_cancel函数取消该线程。
    • 创建线程的进程退出或者整个函数结束。
    • 其中的一个线程执行了exec类函数执行新的进程。


    等待线程


    退出线程示例

    1. #include<pthread.h>  
    2. #include<stdio.h>  
    3. #include<stdlib.h>  
    4. #include<string.h>  
    5. void *helloworld(char *argc);  
    6. int main(int argc,int argv[])  
    7. {  
    8.     int error;  
    9.     int *temptr;      
    10.   
    11.     pthread_t thread_id;      
    12.   
    13.     pthread_create(&thread_id,NULL,(void *)*helloworld,"helloworld");  
    14.     printf("*p=%x,p=%x\n",*helloworld,helloworld);  
    15.     if(error=pthread_join(thread_id,(void **)&temptr))  
    16.     {  
    17.         perror("pthread_join");  
    18.         exit(EXIT_FAILURE);   
    19.     }  
    20.     printf("temp=%x,*temp=%c\n",temptr,*temptr);  
    21.     *temptr='d';  
    22.     printf("%c\n",*temptr);  
    23.     free(temptr);  
    24.     return 0;  
    25. }  
    26.   
    27. void *helloworld(char *argc)  
    28. {  
    29.     int *p;  
    30.     p=(int *)malloc(10*sizeof(int));  
    31.     printf("the message is %s\n",argc);  
    32.     printf("the child id is %u\n",pthread_self());  
    33.     memset(p,'c',10);  
    34.     printf("p=%x\n",p);  
    35.     pthread_exit(p);  
    36.     //return 0;  
    37. }  
    运行结果:

    1. $ ./pthread_exit_test   
    2. *p=80486b9,p=80486b9  
    3. the message is helloworld  
    4. the child id is 3076361024  
    5. p=b6c00468  
    6. temp=b6c00468,*temp=c  
    7. d  

    线程退出前操作

    示例代码:

    1. #include<pthread.h>  
    2. #include<unistd.h>  
    3. #include<stdlib.h>  
    4. #include<stdio.h>  
    5.   
    6. void cleanup()  
    7. {  
    8.     printf("cleanup\n");  
    9. }  
    10. void *test_cancel(void)  
    11. {  
    12.     pthread_cleanup_push(cleanup,NULL);  
    13.     printf("test_cancel\n");  
    14.     while(1)  
    15.     {  
    16.         printf("test message\n");  
    17.         sleep(1);  
    18.     }  
    19.     pthread_cleanup_pop(1);  
    20. }  
    21. int main()  
    22. {  
    23.     pthread_t tid;  
    24.     pthread_create(&tid,NULL,(void *)test_cancel,NULL);  
    25.     sleep(2);  
    26.     pthread_cancel(tid);  
    27.     pthread_join(tid,NULL);  
    28. }  
    运行结果:

    1. $ ./pthread_pop_push   
    2. test_cancel  
    3. test message  
    4. test message  
    5. test message  
    6. cleanup  

    一般来说,Posix的线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。

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

    最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

    在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源 --从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和取消点终止)都将执行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_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),以完成解锁动作。
    work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
    pthread_mutex_lock(&mut);
    /* do some work */
    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);
     }
    

    上面我用红色标记的部分是这两个函数的关键作用,我的理解就是:
    pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
    pthread_mutex_lock(&mut);
    /* do some work */
    pthread_mutex_unlock(&mut);
    pthread_cleanup_pop(0);
    本来do some work之后是有pthread_mutex_unlock(&mut);这句,也就是有解锁操作,但是在do some work时会出现非正常终止,那样的话,系统会根据pthread_cleanup_push中提供的函数,和参数进行解锁操作或者其他操作,以免造成死锁!
    

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

    为避免这种情况可设置线程的可取消状态为PTHREAD_CANCEL_DISABLE,下面将介绍设置线程的可取消状态。



    取消线程


    取消线程是指取消一个正在执行线程的操作,当然,一个线程能够被取消并终止执行需要满足以下条件:
    该线程是否可以被其它取消,这是可以设置的,在Linux系统下,默认是可以被取消的,可用宏分配是PTHREAD_CANCEL_DISABLE和PTHREAD_CANCEL_ENABLE;
    该线程处于可取消点才能取消。也就是说,该线程被设置为可以取消状态,另一个线程发起取消操作,该线程并不是一定马上终止,只能在可取消点才中止执行。可以设置为立即取消和在取消点取消。可用宏为PTHREAD_CANCEL_DEFERRED和PTHREAD_CANCEL_ASYNCHRONOUS。

    设置可取消状态 (默认情况下,在创建某个线程时,其可取消性状态为PTHREAD_CANCEL_ENABLE


    可设置的state的合法值:

    • 如果目标线程的可取消性状态为PTHREAD_CANCEL_DISABLE,则针对目标线程的取消请求将处于未决状态,启用取消后才执行取消请求。
    • 如果目标线程的可取消性状态为PTHREAD_CANCEL_ENABLE,则针对目标线程的取消请求将被传递。默认情况下,在创建某个线程时,其可取消性状态设置为PTHREAD_CANCEL_ENABLE。

    设置取消类型

    pthread_setcanceltype()函数用来设置取消类型,即允许取消的线程在接收到取消操作后是立即中止还是在取消点中止,该函数声明如下:

    1. extern int pthread_setcanceltype (int __type, int *__oldtype)  
    此函数有两个参数,type为调用线程的可取消性类型所要设置的值。oldtype为存储调用线程原来的可取消性类型的地址。type的合法值包括:
    如果目标线程的可取消性状态为PTHREAD_CANCEL_ASYNCHRONOUS,则可随时执行新的或未决的取消请求。
    如果目标线程的可取消性状态为PTHREAD_CANCEL_DEFERRED,则在目标线程到达一个取消点之前,取消请求将一直处于未决状态。
    在创建某个线程时,其可取消性类型设置为PTHREAD_CANCEL_DEFERRED。

    示例代码

    1. #include <stdio.h>  
    2. #include <unistd.h>  
    3. #include <stdlib.h>  
    4. #include <pthread.h>  
    5.   
    6. void *thread_function(void *arg);  
    7.   
    8. int main(int argc,char *argv[])   
    9. {  
    10.     int res;  
    11.     pthread_t a_thread;  
    12.     void *thread_result;  
    13.   
    14.     res = pthread_create(&a_thread, NULL, thread_function, NULL);  
    15.     if (res != 0)   
    16.    {  
    17.         perror("Thread creation failed");  
    18.         exit(EXIT_FAILURE);  
    19.     }  
    20.   
    21.     printf("Cancelling thread...\n");  
    22.     sleep(10);    
    23.     res = pthread_cancel(a_thread);  
    24.     if (res != 0)   
    25.    {  
    26.         perror("Thread cancelation failed");  
    27.         exit(EXIT_FAILURE);  
    28.     }  
    29.   
    30.     printf("Waiting for thread to finish...\n");  
    31.     sleep(10);    
    32.     res = pthread_join(a_thread, &thread_result);  
    33.     if (res != 0)   
    34.     {  
    35.         perror("Thread join failed");  
    36.         exit(EXIT_FAILURE);  
    37.     }  
    38.     exit(EXIT_SUCCESS);  
    39. }  
    40.   
    41. void *thread_function(void *arg)   
    42. {  
    43.     int i, res, j;  
    44.     sleep(1);  
    45.     res = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);  
    46.     if (res != 0)   
    47.     {  
    48.         perror("Thread pthread_setcancelstate failed");  
    49.         exit(EXIT_FAILURE);  
    50.     }  
    51.     printf("thread cancle type is disable,can't cancle this thread\n");  
    52.     for(i = 0; i <3; i++)   
    53.     {  
    54.         printf("Thread is running (%d)...\n", i);  
    55.         sleep(1);  
    56.     }  
    57.   
    58.     res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);  
    59.     if (res != 0)   
    60.     {  
    61.         perror("Thread pthread_setcancelstate failed");  
    62.         exit(EXIT_FAILURE);  
    63.     }  
    64.     else  
    65.     printf("Now change ths canclestate is ENABLE\n");  
    66.    sleep(200);   
    67.    pthread_exit(0);  
    68. }  
    运行结果:

    1. $ ./pthread_cancle_example   
    2. Cancelling thread...  
    3. thread cancle type is disable,can't cancle this thread  
    4. Thread is running (0)...  
    5. Thread is running (1)...  
    6. Thread is running (2)...  
    7. Now change ths canclestate is ENABLE  
    8. Waiting for thread to finish...  

    线程与私有数据

    在多线程程序中,经常要用全局变量来实现多个函数间的数据共享。由于数据空间是共享的,因此全局变量也为所有进程共有。但有时应用程序设计中必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。
    比如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由 Posix 线程库维护,成为线程私有数据 (Thread-specific Data,或称为 TSD)。

    主要用于:一个全局的数据,各个线程函数中需要用到这个数据并在线程函数中改变该数据的值,但是不影响主进程中对该数据的使用。假设定义一个全局的key,在主进程A中设置key=0,在A中创建两个线程B,C,线程B中让key=1,线程C中让key=2,那么如果key不设置为线程的私有数据的话,当线程BC结束后key的值就为2了,且如果C对key的赋值在B的后面执行的话,C在对key赋值之前key的值就为1了。而如果把key设置为线程私有数据的话,线程BC结束后key的值仍然为0,而且如果C对key的赋值在B的后面执行的话,C在对key赋值之前key的值仍然为0。
    这里主要测试和线程私有数据有关的 4 个函数:

    1. pthread_key_create();  //创建私有数据,参数一为要创建的私有数据,传入的为地址,参数二为回调函数
    2. pthread_key_delete();  //参数为创建的私有数据
    3.   
    4. pthread_getspecific(); //获取私有数据的值 
    5. pthread_setspecific();  //设置私有数据的值

    示例代码(使用全局同名变量的情况):

    1. #include<stdio.h>  
    2. #include<pthread.h>  
    3. #include<unistd.h>  
    4. #include<stdlib.h>  
    5.   
    6. int key=100;  
    7. void *helloworld_one(char *argc)  
    8. {  
    9.     printf("the message is %s\n",argc);  
    10.     key=10;  
    11.     printf("key=%d,the child id is %u\n",key,pthread_self());  
    12.     return 0;  
    13. }  
    14.   
    15. void *helloworld_two(char *argc)  
    16. {  
    17.     printf("the message is %s\n",argc);  
    18.     sleep(1);  
    19.     printf("key=%d,the child id is %u\n",key,pthread_self());  
    20.     return 0;  
    21. }  
    22. int main()  
    23. {  
    24.   
    25.     pthread_t thread_id_one;  
    26.     pthread_t thread_id_two;  
    27.   
    28.     pthread_create(&thread_id_one,NULL,(void *)*helloworld_one,"helloworld");  
    29.     pthread_create(&thread_id_two,NULL,(void *)*helloworld_two,"helloworld");  
    30.     pthread_join(thread_id_one,NULL);  
    31.     pthread_join(thread_id_two,NULL);  
    32. }  
    运行结果:

    1. $ ./pthread_glob_test   
    2. the message is helloworld  
    3. the message is helloworld  
    4. key=10,the child id is 3075849024  
    5. key=10,the child id is 3067456320  

    示例代码(使用线程私有数据的情况):

    1. //this is the test code for pthread_key   
    2.   
    3. #include <stdio.h>   
    4. #include <pthread.h>   
    5.   
    6. pthread_key_t key=0;   
    7. void echomsg(void *t)  //回调函数, t为key
    8. {   
    9.     printf("destructor excuted in thread %u,param=%u\n",pthread_self(),((int *)t));   
    10. }   
    11.   
    12. void * child1(void *arg)   
    13. {   
    14.     int i=10;  
    15.     int tid=pthread_self();   
    16.     printf("\nset key value %d in thread %u\n",i,tid);  
    17.     sleep(2);
    18. //此时key的值仍然为0; 
    19.     pthread_setspecific(key,&i);   
    20.     printf("thread one sleep 2 until thread two finish\n");  
    21.     sleep(2);   
    22.     printf("\nthread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key));   
    23. }   
    24.   
    25. void * child2(void *arg)   
    26. {   
    27.     int temp=20;  
    28.     int tid=pthread_self();   
    29.     printf("\nset key value %d in thread %u\n",temp,tid);   
    30.     pthread_setspecific(key,&temp);   
    31.     sleep(1);   
    32.     printf("thread %u returns %d,add is %u\n",tid,*((int *)pthread_getspecific(key)),(int *)pthread_getspecific(key));   
    33. }   
    34.   
    35. int main(void)   
    36. {   
    37.     pthread_t tid1,tid2;   
    38.     pthread_key_create(&key,echomsg);//echomsg为回调函数,当线程执行完成后调用echomsg,echomsg的参数t则为key,
    39.     pthread_create(&tid1,NULL,(void *)child1,NULL);   
    40.     pthread_create(&tid2,NULL,(void *)child2,NULL);   
    41.     pthread_join(tid1,NULL);  
    42.     pthread_join(tid2,NULL);  
    43.    //此时key的值仍然为0
    44.     pthread_key_delete(key);   
    45.     return 0;   
    46. }   
    运行结果:

    1. $ ./pthread_key_test   
    2.   
    3. set key value 20 in thread 3067337536  
    4.   
    5. set key value 10 in thread 3075730240  
    6. thread one sleep 2 until thread two finish  
    7. thread 3067337536 returns 20,add is 3067335512  
    8. destructor excuted in thread 3067337536,param=3067335512  
    9.   
    10. thread 3075730240 returns 10,add is 3075728216  
    11. destructor excuted in thread 3075730240,param=3075728216  

    原文链接:

    http://blog.csdn.net/geng823/article/details/41285251

    展开全文
  • 关键词:线程资源释放, pthread_join() ...3,线程正常退出的方式有三种: 线程函数直接return, 被同一进程的其他线程调用pthread_cancel取消,线程调用pthread_exit 退出,(线程运行异常退出

    关键词:线程资源释放, pthread_join()

    1, 每个线程有自己的一组寄存器,自己的栈空间, 自己的errno变量;

    2,pthread_create线程创建并不能保证哪个线程先运行,新线程还是调用线程。

    3,线程正常退出的方式有三种: 线程函数直接return, 被同一进程中的其他线程调用pthread_cancel取消,线程调用pthread_exit 退出,(线程运行异常退出)。

    4,不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题,https://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part4/ 一文中介绍了pthread_cleanup_push()/pthread_cleanup_pop()解决资源释放问题,因为没有用到,不写在这里,对于异常退出的情况,注意在每个退出的点上都加上资源,尤其是锁资源的释放。

    5,对于线程正常退出的资源释放,根据线程的属性,有两种方式: 对于属性为joinable的(默认),线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放对于属性为detached分离状态的,线程在结束运行时自行释放所占用的内存资源。

    6,把线程置为detached属性有两种方式,一种是在线程创建时设置属性pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);另一种是调用pthread_detach()使线程成为detached,就可以自动释放资源了。

    7,对于属性为joinable的线程,可以通过调用pthread_detach()将其属性改变为detached, 但是属性为detached的线程,不能改回joinable属性, 也就是不能调用pthread_join()释放资源。

    8,如果线程pthread_detach()执行之后,对线程请求pthread_join()将返回错误,如果线程已经被调用pthread_join()后,再调用pthread_detach()就不会有任何效果。

    9,一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收

    10,pthread_join()函数:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

    展开全文
  • 线程退出有三种方式: (1)执行完成后隐式退出; (2)由线程本身显示调用pthread_exit 函数退出;  pthread_exit (void * retval) ;  (3)被其他线程用pthread_cance函数终止:  pthread_cance ...

    多线程退出有三种方式:
    (1)执行完成后隐式退出;

    (2)由线程本身显示调用pthread_exit 函数退出;
        pthread_exit (void * retval) ; 

    (3)被其他线程用pthread_cance函数终止:
        pthread_cance (pthread_t thread) ; 


    用event来实现。

    在子线程中,在循环内检测event。
    while(!e.is_active())
    {
      ...
    }
    当退出循环体的时候,自然return返回。这样子线程会优雅的结束。

    注意:选用非等待的检测函数。


    pthread 线程有两种状态,joinable(非分离)状态和detachable(分离)状态,默认为joinable。

      joinable:当线程函数自己返回退出或pthread_exit时都不会释放线程所用资源,包括栈,线程描述符等(有人说有8k多,未经验证)。

      detachable:线程结束时会自动释放资源。

    Linux man page said:

    When a joinable thread terminates, its memory resources (thread descriptor and stack) are not deallocated until another thread performs pthread_join on it. Therefore, pthread_join must be called  once  for each joinable thread created to avoid memory leaks.

    因此,joinable 线程执行完后不使用pthread_join的话就会造成内存泄漏。

    解决办法:

    1.// 创建线程前设置 PTHREAD_CREATE_DETACHED 属性

    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);
    pthread_attr_destroy (&attr);


    2.当线程为joinable时,使用pthread_join来获取线程返回值,并释放资源。

    3.当线程为joinable时,也可在线程中调用 pthread_detach(pthread_self());来分离自己。


    =========================================================================


    /********************************** pthread_exit.c **************************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <errno.h>

    void exe_exit1(void *arg)
    {
        printf("我是线程退出时要执行的程序1!");
        printf("传递给我的参数是:%s\n",(char *)arg);
    }

    void exe_exit2(void *arg)
    {
        printf("我是线程退出时要执行的程序2!");
        printf("传递给我的参数是:%s\n",(char *)arg);
    }

    void *func()
    {
        int ret;
        
        ret=pthread_detach(pthread_self());/*使本线程成为分离状态的线程*/
        if(ret==0)
            printf("pthread_detach调用成功!\n");
        else
            printf("pthread_detach调用出错,错误编号:%d",ret,errno);
            
        printf("我是线程,已经开始启动!\n");
        pthread_cleanup_push(exe_exit2,"给exe_exit2的参数");
        pthread_cleanup_push(exe_exit1,"给exe_exit1的参数");
        printf("pthread_cleanup_push设置完成。\n");
        printf("我是线程,即将退出。\n");
        pthread_exit((void *)0);
        pthread_cleanup_pop(0);/*下面两行不能少,否则会编译不通过,提示语法错误*/
        pthread_cleanup_pop(0);
    //    printf("pthread_cleanup_pop设置完成。\n");
    }

    int main()
    {
        int ret;
        pthread_t tid;
        
        pthread_create(&tid,NULL,func,NULL);
        
        sleep(2);
        exit(0);
    }

    编译: gcc -o pthread_exit -lpthread pthread_exit.c
    执行: ./pthread_exit

    ///////////////////////////////// 运行结果 //////////////////////////////////
    pthread_detach调用成功!
    我是线程,已经开始启动!
    pthread_cleanup_push设置完成。
    我是线程,即将退出。
    我是线程退出时要执行的程序1!传递给我的参数是:给exe_exit1的参数
    我是线程退出时要执行的程序2!传递给我的参数是:给exe_exit2的参数

    几个需要说明的地方:

    1、pthread_cleanup_pop的参数是0的话,表示不执行线程清理处理程序(但并不妨碍把栈顶存放的那个处理程序的记录弹出,只是不执行而已),即这里的exe_exit1和exe_exit2。因此,如果程序中的这一句pthread_exit((void *)0);移到func函数结尾的话,exe_exit1和exe_exit2函数将不会得到执行,因为之前的两个pthread_cleanup_pop函数已经把存储的处理程序的记录给清空了。再执行到pthread_exit((void *)0);的时候栈中已经没有待执行的处理函数了。

    2、虽然本程序中的pthread_cleanup_pop函数的参数是0,这两句本身不会触发线程清理处理程序(触发线程清理处理程序的任务是由pthread_exit来完成的)。但是这两句不能去掉,可以试一下把这两句注释掉,再编译的话就会出现错误。原因估计应该是pthread_cleanup_pop必须要和pthread_cleanup_push成对出现吧。

    3、我一开始把pthread_detach函数放在了main函数中,结果运行时总是提示Segmentation fault(段错误)。后来在网上搜索后,改为把pthread_detach函数在func函数中调用,问题解决。 


    展开全文
  • 在了解线程与进程之间的区别前我们先来认识线程与进程,我们从概念入手,什么是线程、什么是进程,再进行进程与线程之间的不同点与相同点比较,优点与缺点进行比较理解。 进程的概念 进程(Process)就是程序的一...

    进程与线程的区别

    在了解线程与进程之间的区别前我们先来认识线程与进程,我们从概念入手,什么是线程、什么是进程,再进行进程与线程之间的不同点与相同点比较,优点与缺点进行比较理解。

    进程的概念

    进程(Process)就是程序的一次实例化,是分配资源的实体
    进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动
    是资源分配的基本单位

    但是如果单单这样理解是完全不够的,我们必须要深入理解,怎么理解?
    我们来结合进程中的PCB来理解,一个进程中有三大块,进程控制块(PCB)、数据段、代码段。我们主要分析PCB

    首先在LInux下,用来描述PCB的是一个叫 task_struct 的结构体,来描述一个PCB,从而操作系统通过PCB来管理一个进程。

    我们来看看 task_struct 主要的信息

    1. 标识符:用来描述本进程的唯一标识符,区别其他进程
    2. 状态:用来表示当前进程的状态,退出码,退出信号
    3. 优先级:相对于其他进程之间的优先级
    4. 程序计数器:用于记录程序的下一条指令的地址
    5. 内存指针:其中有代码和数据相关的指针,还有共享内存块指针
    6. 上下文:进程执行时寄存器中的数据 (用来程序切换是记录的上下文)
    7. I/O状态 :包括显示I/O请求,分配I/O设备和被进程使用的文件列表
    8. 记账信息:其中包含各种时间
    9. 其他

    我么可以在Linux下用 top 命令查看当前进程
    进程
    我们再看看进程的特征

    动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
    并发性:任何进程都可以同其他进程一起并发执行
    独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源的独立单位;
    异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
    结构特征:进程由程序、数据和进程控制块三部分组成。

    线程的概念

    什么是线程?

    线程是进程中一个控制序列,每个进程都最少有一个线程,线程是调度的基本单位。在linux中线程也是一个轻量级的进程。

    线程因为是轻量级进程也有三大块,线程控制块(在linux中是因为线程就是轻量级进程所以线程的控制块也叫PCB)、数据段、代码段。但是大部分都是共享的。
    我们先看看在Linux下线程
    线程
    在上面图中,PID为进程的ID、PPID为进程的父进程ID、LWP为线程的ID
    简单介绍线程,我们来对比着学习

    线程与进程的区别与联系

    区别

    • 资源分配:资源分配的基本单位,而线程是调度的基本单位

    • 进程与进程之间是独立的,一个进程的异常终止不会影响其它进程,而线程与线程之间大部分是共享的,一个线程的异常终止会影响其它线程,会使进程终止。

    • 线程与线程大部分共享,但是也有一部分数据私有,线程ID、上下文(切换时候寄存器中的值)、自己独享一个栈空间、错误码、信号屏蔽字、调度的优先级。

    • 调度和切换:线程上下文切换比进程上下文切换要快得多。

    • 一个进程中有多个线程时候,线程共享以下,在进程与进程之间的切换所花费消耗的大于线程与线程之间切换的花销

    • 线程比进程占用的资源要小,一个线程的创建远小于进程的创建。
    • 进程与进程之间是独立的,所以在并发过程中,用同步互斥少,相对安全,代码编写容易。线程之间大多数资源共享,所以往往要加上同步互斥锁。
    • 线程创建出来的线程是平等的没有上下级,而进程创建出进程就为该进程的子进程

    联系

    • 进程与线程之间的关系:线程是存在进程的内部,一个进程中可以有多个线程,一个线程只能存在一个进程中。
    • 一个线程的结束进程不一定会退出,但是进程的退出,线程将退出。
    • 一个进程中的多个线程共享:1)一个进程中线程与线程之间同的虚拟地址空间、2)共享文件描述符、3)每种信号的处理方式、4)当前的工作目录、5)用户ID和组ID
    • 进进程资源,防止造成僵尸进程。而线程也要进行等待,释放线程的资源(除过线程的分离)

      线程ID和在进程地址空间的布局

      线程ID在线程库NPTL中提供的pthread_create创建出来的是一个地址,我们可以通过

    pid_t gettid(void) // 获取线程ID

    通过

    pid_t getpid(void) // 获取进程ID

    进程的虚拟地址空间既线程在虚拟地址空间中的布局,我们来画一个图来解释
    线程在虚拟地址空间中的存储
    我们前面讲过,线程在进程中,那么线程中的自己私有的东西,是存放在mmap内存共享区中的。
    那么线程ID为在每个维护自己线程的起始位置的地址。

    线程的创建、等待与退出

    我们是采用用POSIX线程库,来进行一些相关函数接口的解释,所以我们要在编译的时候添加上 -lpthread

    线程的创建

    先看函数

    #include <pthread.h>
    int phread_t create(pthread_t *thread, const pthread_attr* attr,\
    void *(startt_routine)(void*), void * arg);

    参数解释
    thread:返回线程ID
    attr:设置线程的属性,使用默认属性设置为NULL
    start_routine:函数地址,是线程启动的入口函数
    arg:传给线程入口函数的参数
    返回值:成功返回0,失败返回错误码

    我们调用函数就可以创建一个线程,如果计算机是多核计算机,那么创建出来的线程和原有的线程是并行执行。

    线程的等待

    线程等待,和前面将的进程等待的目的很相似,都是为了防止内存泄漏。线程是在进程中的虚拟地址空间的mmap地址共享区,当线程的创建会在共享区,创建出自己的栈和相关的数据,是自己私有的,所以当线程退出的时候,必须要释放,所以就需要进程的等待
    等待函数

    #include <phtread.h>
    int pthread_join(pthread_t thread, void **value_ptr);

    参数
    thread:线程ID
    value_ptr:指向的是线程返回值(输出型参数)
    返回值:成功返回0,失败返回错误码。

    如果对线程的终止状态不感兴趣,可以传NULL

    线程的退出

    我们类比进程的退出,进程有三种情况
    1)程序执行完了结果正确
    2)程序执行完了结果不正确
    3)程序执行异常终止

    那么线程相比进程来说,要简单一些
    1)线程执行完了
    2)线程异常终止
    看线程的终止函数

    #include <pthread.h>
    // 线程的终止
    void pthread_exit(void* value_ptr);

    参数
    value_ptr:线程结束的返回值,不可以用局部变量返回

    #include <phtread.h>
    // 一个线程将一个正在执行的线程取消掉
    int pthread_cancel(pthread_t thread);

    参数
    thread:需要终止的线程ID

    还有一个就是线程的函数的返回 return 。
    下面我们来用代码具体演示一下:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    void *pthread_fun(void *arg)
    {
        (void)arg;
        int i = 10;
        while (i--)
        {
            sleep(1);
            printf("i am thread\n");
            // 如果要退出需要返回一个值,这个值一定不能在栈上
        //    pthread_exit(NULL);
        }
        pthread_t th = pthread_self();
        printf("%lu\n", th);
    
        // 如果要退出需要返回一个值,这个值一定不能在栈上
        return NULL;
    }
    
    int main()
    {
        pthread_t thr;
        // 线程创建成功返回0,失败返回错误码
        // 传入thr, 线程属性,线程函数返回值为void*,线程函数参数为void*
        int ret = pthread_create(&thr, NULL, pthread_fun, NULL);
        if (ret != 0)
        {
            printf("error %d\n", ret);
            exit(1);
        }
        int i = 5;
        while (i--)
        {
            sleep(1);
            printf("i am main thread\n");
        }
        // 线程的等待,第二参数为输出型参数,输入线程的返回值
        // 有返回值,成功返回0,失败返回错误码
        // 为阻塞式等待
        /* void* value; */
        // 这里可以接收pthread_exit(void* value)中的value,如果是空就必须在join中为NULL
        pthread_join(thr, NULL); //输入型参数类型为void**
        /* printf("join value %d\n", *(int*)value); // 这里要先强转为int*在解引用 */
        return 0;
    }

    结果

    最后我们了解一下线程的分离。
    线程分离为了提高程序运行的效率,有时候我们不关心线程退出的状态,所以就没必要等待线程的退出,那么前面所说的释放资源呢?当一个线程被设置为线程分离的时候,线程运行完成后,自动退出,并且释放资源。

    int pthread_delete(pthread_t thread);

    如有错误,还望多多指导!谢谢!

    展开全文
  • 1. 什么是线程   线程是进程执行内部的一个执行分支,在一个进程内部运行的多种执行流;内部本质上是多个线程在同一个地址空间运行;第一个pcb称之为主线程;有多个线程就有多个执行流;一个进程至少有一个线程 ...
  • 线程主动调用pthread_exit()或者从线程函数return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其他线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。...
  • 最近项目遇到循环8M的内存泄漏问题,排查后发现是由于特殊场景下使子线程异常退出,主线程发现子线程退出便再次创建线程,此时未回收退出线程的资源造成了内存泄漏,导致数据库宿主机oom,此问题不仅导致内存泄漏...
  • 线程 1.是操作系统能够进行调度的最小单位 2.线程被包含在进程之,是进程的实际运作单位 3.一个线程指的是进程一个单一顺序的控制流 4.一个进程可以并发多个线程,每个线程执行不同的任务 比如四...
  • Linux-线程学习(上)

    2018-04-21 13:18:04
    我们写的程序从硬盘加载到内存开始运行时,进程就产生了。也就是操作系统开始为这个程序创建PCB,分配系统资源,比如分配一块虚拟地址空间,一个页表,一块物理内存。当这个进程内部有多个执行流时,现
  • Linux线程部分总结分为两部分:(1)线程的使用 ,(2)线程的同步与互斥。 第一部分线程的使用主要介绍,线程的概念,创建线程,线程退出,以及线程的终止与分离。第二部分主要介绍在多线程环境下,使用同步与互斥...
  • t 甚或几 p 的数据的数据库系统,到手机上的一个有良好用户响应能力的 app,为了充分利用每个 CPU 内核,都会想到是否可以使用多线程技术。这里所说的“充分利用”包含了两个层面的意思,一
  • Linux线程概念 线程的概念 线程是计算机科学的一个术语,是指运行中的程序的调度单位。一个线程指的是进程一个单一顺序的控制流,也称为轻量进程。它是系统独立调度和分配的基本单位。同一进程的多个线程...
  • 如果 子线程出现异常问题 如果不在父线程中进行捕获异常 该子线程也不进行异常捕获的话 则子线程一但出现异常 则此子线程就关闭了 父线程 并不知情 可能认为 该子线程已经正常 运行完毕了呢 所以 突显出父线程捕获...
  • linux线程编程示例

    2017-10-12 15:18:22
     线程是指运行中的程序的调度单位。一个线程指的是进程一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程的多个线程将共享该系统的全部系统资源,比如文件描述符和信号...
  • linux线程编程是指基于Linux操作系统下的多线程编程,包括多任务程序的设计,并发程序设计,网络程序设计,数据共享等。Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用...
  • 终止线程的三种方法   有三种方法可以使终止线程。   1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。   2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend...
  • 【摘要】本文首先介绍了进程和线程的区别,接着分析了内核线程、轻量级LWP线程以及常见的用户线程的特点,同时介绍了内核线程和进程的区别。分析了创建内核线程kernel_thread函数的实现过程,介绍了一个在驱动使用...
  • 如果在你试图退出一个线程时,该线程中还有未释放的资源,这时应该怎么处理呢? 在cancel一个线程时,线程体可能并不会立即退出,这就会出现两个问题: 在调用cancel之后,如果线程体仍在运行,就会导致pthread_...
  • 线程是指运行中的程序的调度单位。一个线程指的是进程一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程的多个线程将共享该系统的全部系统资源,比如文件描述符和信号...
1 2 3 4 5 ... 20
收藏数 35,169
精华内容 14,067