正在加载……
专注、离线、切勿分心

1.等待线程退出:

          线程从入口点函数自然返回,或者主动调用pthread_exit()函数,都可以让线程正常终止。

          线程从入口点函数自然返回时,函数返回值可以被其它线程用pthread_join函数获取。

pthread_join原型为:

      #include <pthread.h>

   int pthread_join(pthread_t th, void **thread_return);


1.该函数是一个阻塞函数一直等到参数 th 指定的线程返回与多进程中的waitwaitpid类似。

thread_return是一个传出参数,接收线程函数的返回值。如果线程通过调用pthread_exit()终止,则 pthread_exit() 中的参数相当于自然返回值,照样可以被其它线程用pthread_join()获取到


2.thid传递0值时,join返回ESRCH错误。

3.该函数还有一个非常重要的作用,由于一个进程中的多个线程共享数据段,因此通常在一个线程退出后,退出线程所占用的资源并不会随线程结束而释放。如果th线程类型并不是自动清理资源类型的,则th线程退出后,线程本身的资源必须通过其它线程调用pthread_join来清除,这相当于多进程程序中的waitpid
pthread_join.c
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
void* threadfunc(void*p)
{
        strcpy((char*)p,"hello");
        printf("I am a child thread %s\n",(char*)p);
        printf("child p is %p\n",p);
        pthread_exit(p);
}
int main()
{
        pthread_t thdid;
        void* p=malloc(20);
        printf("father p is %p\n",p);
        int ret = pthread_create(&thdid,NULL,threadfunc,p);
        if(ret!=0)
        {
                printf("error pthread_create\n");
                return -1;
        }
        printf("I am a father thread %s\n",(char*)p);
        void *p1;
        printf("return thread  p1 is %p\n",p1);
        ret=pthread_join(thdid,&p1);
        if(ret!=0)
        {
                printf("error pthread_join\n");
                return -1;
        } 
        printf("father thread %s\n",(char*)p);
        printf("return thread  p1 is %p\n",p1);
        printf("retturn thread p1=%s\n",(char*)p1);
        return 0;
}




2. 线程取消

线程也可以被其它线程杀掉,在Linux中的说法是一个线程被另一个线程取消(cancel)

       线程取消的方法是一个线程向目标线程发cancel信号,但是如何处理cancel信号则由目标线程自己决定,目标线程或者忽略、或者立即终止、或者继续运行cancelation-point(取消点)后终止


取消点:

       根据POSIX标准,pthread_join()pthread_testcancel()pthread_cond_wait()pthread_cond_timedwait()sem_wait()sigwait()等函数以及read()write()等会引起阻塞的系统调用都是Cancelation-point而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于Linux线程库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:

    

pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();

     

      但是从RedHat9.0的实际测试来看,至少有些C库函数的阻塞函数是取消点,如read(),getchar()等,而sleep()函数不管线程是否设置了pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL),都起到取消点作用。总之,线程的取消一方面是一个线程强行杀另外一个线程,从程序设计角度看并不是一种好的风格,另一方面目前Linux本身对这方面的支持并不完善,所以在实际应用中应该谨慎使用!!


       int pthread_cancel(pthread_t thread);         //尽量不要用,linux支持并不完善


 

man函数不能查看函数帮助增加man信息 sudo apt-get install manpages-posix-dev
pthread_cancel.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
//验证主线程cancel子线程场景
void* thread(void* p)
{
        printf("I am child\n");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        //sleep(2);    //让线程正常执行完退出
        ret=pthread_cancel(pth_id);  //不用等待让线程创建好,传入pth_id可以直接取消后续操作
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        int i;
        ret=pthread_join(pth_id,(void**)&i);    //不获取子线程的返回值
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread i = %d\n",(int)i);
        return 0;
}


#define        ESRCH                 3        /* No such process */
线程被取消,退出值就是-1,这个值还是能被pthread_join函数捕捉到


所有的内核线程运行在同一个内核进程地址空间,这个和用户级线程不一样



3. 线程终止清理函数

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

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

       POSIX线程API中提供了一个pthread_cleanup_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)                //(默认都写0)


       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()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。;宏定义中'\'表示连接到下一行;

pthread_cleanup_pop 的参数 execute 如果为非0,则按栈的顺序注销掉一个原来注册的清理函数,并执行该函数;当 pthread_cleanup_pop() 函数的参数为0时,仅仅在线程调用pthread_exit函数或者其它线程对本线程调用pthread_cancel函数时,才在弹出清理函数的同时执行该清理函数

pthread_cleanup.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

//验证子线程被cancel后,能够执行线程清理函数
void clean1(void* p)
{
        printf("I am clean func %d\n",(int)p);
}

void* thread(void* p)
{
        pthread_cleanup_push(clean1,(void*)1);
        printf("I am child\n");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);
        pthread_cleanup_pop(0);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        ret=pthread_cancel(pth_id);
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        int i;
        ret=pthread_join(pth_id,(void**)&i); 
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread i =%d\n",(int)i);
        return 0;
}


pthread_cleanup2.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void clean1(void* p)
{
        printf("I am clean func %d\n",(int)p);
}

void* thread(void* p)
{
        pthread_cleanup_push(clean1,(void*)1);
        pthread_cleanup_push(clean1,(void*)2);
        printf("I am child\n");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);
        pthread_cleanup_pop(0);
        pthread_cleanup_pop(0);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        ret=pthread_cancel(pth_id);
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        void* p;
        ret=pthread_join(pth_id,&p);  
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread p=%d\n",(int)p);
        return 0;
}



//子线程动态申请空间,被其他线程取消,可以调用清理函数清理
pthread_clean_malloc.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

void clean1(void* p)
{
        printf("I am clean,p =%s\n",(char*)p);
        free(p);   //由清理函数进行堆内存free
}

void* thread(void* p)
{
        p=malloc(20);
        pthread_cleanup_push(clean1,p);
        printf("I am child\n");
        strcpy((char*)p,"hello");
        char buf[128]={0};
        read(0,buf,sizeof(buf));
        printf("after read\n");
        pthread_exit((void*)5);  //如果没有这一句,要执行清理函数就要把下面参数改为非0
        pthread_cleanup_pop(0);
}

int main()
{
        pthread_t pth_id;
        pthread_create(&pth_id,NULL,thread,NULL);
        int ret;
        sleep(2);
        ret=pthread_cancel(pth_id);
        if(ret!=0)
        {
                printf("pthread_cancel failed ret=%d\n",ret);
                return -1;
        }
        void* q;
        ret=pthread_join(pth_id,&q);  //不获取子线程的返回值
        if(ret!=0)
        {
                printf("pthread_join failed ret=%d\n",ret);
                return -1;
        }
        printf("main thread q=%d\n",(int)q);
        return 0;
}


// 第一个结果是被中途取消了的,退出值-1,;第二个结果是线程运行到pthread_exit((void*)5)退出,退出值就是5







posted on 2018-03-04 09:10  正在加载……  阅读(2329)  评论(0编辑  收藏  举报