一、线程终止时与进程的关系
  • ①如果进程中的任意线程调用了exit、_Exit、_exit,那么整个进程就会终止
  • ②如果线程中某个信号的默认动作是终止进程,那么,发送到某个线程的信号就会终止整个进程(在javascript:void(0)文章中会详细介绍线程与信号的处理)
二、线程的终止方式

下面这三种方法是正常不终止整个进程的情况下,终止线程并且停止它的控制流:

  • ①线程可以简单地从启动例程中返回,返回值是线程的退出码
  • ②线程可以被同一进程中的其他线程取消(pthread_cancel)
  • ③线程调用pthread_exit

下面是其他线程终止的情况:

  • ①创建线程的进程退出,那么线程自然就没有了(因此没有孤儿线程的说法,只有孤儿进程的说法)
  • ②其中一个线程执行exec,因为会替换当前进程所有的地址空间。子线程退出仅释放自己私有空间,私有栈空间
三、线程退出函数(pthread_exit)
#include <pthread.h>
void pthread_exit(void *rval_ptr);
  •  功能:线程调用此函数终止自己
  • 参数:rval_ptr是一个无类型指针,退出进程可以在这个指针里面设置内容(常用来设置终止码)。进程中的其他线程也可以通过调用pthread_join函数访问这个指针
四、线程等待函数(pthread_join)
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);

//返回值:成功返回0;失败返回错误编号
  • 功能:用来等待参数1指定的线程结束
  • 此函数会阻塞,直到指定的线程调用pthread_exit、从启动线程中返回、或者被取消,此函数才返回

参数:

  • 参数1:指定等待的线程的ID
  • 参数2:
    • 填NULL:获取等待线程的终止信息,如果对线程的终止信息并不敢兴趣,可以设置为NULL。
    • 非空:如果线程简单地从它的返回例程中返回,rval_ptr 就包含返回码。如果线程被取消,由rval_ptr指定的内存单元就被设置为PTHREAD_CANCELED

pthread_join函数的使用与线程分离的关系(重要):

  • 调用pthread_join等待某一线程,被等待的线程结束之后就会被置于分离状态,这样线程所使用的资源就可以恢复
  • 如果调用pthread_join等待一个线程时,如果线程已处于分离状态(例如调用pthread_detach函数),pthread_join调用会失败,返回EINVAL,尽管这种行为是与具体实现相关的

pthread_exit()参数与pthread_join()参数的注意事项(重点):

  • pthread_create 和 pthread_exit 函数的无类型指针参数可以传递的值不止一个,这个指针可以传递包含复杂信息的结构体地址,但是注意,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的
  • 例如,在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内容可能已经改变了
  • 又如,线程在自己的栈上分配了一个结构,然后把指向这个结构的指针传给pthread_exit,那么调用 pthread_join 的线程试图使用该结构时,这个栈有可能已经被撤销,这块内存也另做他用(例如下面“十三”的演示案例中就有关于这个方面的介绍)
  • 在pthread_join函数中,用一个申请的堆变量来保存线程的退出码,那么在获取了退出码之后,pthread_join函数会自动释放这个保存线程退出码的堆变量,因此我们自己申请的堆变量不需要手动free释放。为了防止内存丢失,可以申请栈变量来保存线程的退出码(例如下面“十四”的演示案例中就有关于这个方面的介绍)
五、线程取消函数(pthread_cancel)
#include <pthread.h>
int pthread_cancel(pthread_t tid);

//返回值:成功返回0;否则返回错误编号
  • 功能:线程可以通过pthread_cancel来请求取消同一进程中的其它线程
  • pthread_cancel并不等待线程终止,它仅仅提出请求

参数:

  • 需要取消的线程ID

注意事项:

  • 默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是被取消的线程可以选择忽略取消或者控制如何被取消(见下面的一系列函数)
六、线程取消点
  • 概念:系统自定义了一些线程取消点。当一个线程接收到其他线程的取消请求时,如果还没有运行到取消点,该线程还是会继续运行,直到运行到某个取消点,线程才真正地被取消
  • 线程取消点技术也称为“推迟取消”

下图是POSIX.1定义的取消点:

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_join

七、线程取消选项(pthread_setcancelstate)
 #include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);

//返回值:若成功返回0;失败返回错误编号
  • 功能:用于设置线程在响应pthread_cancel函数时所呈现的行为,用于设置线程的取消选项

参数:

  • state:新设置的线程取消选项(见下面)
  • oldstate:用来保存原先的线程取消选项

取消选项值:

  • PTHREAD_CANCEL_ENABLE:(线程启动时的默认值)线程接收到另一个线程的pthread_cancel取消请求时,线程被杀死终结
  • PTHREAD_CANCEL_DISABLE:设置此选项后,线程接收到另一个线程的pthread_cancel取消请求时,线程并不会被杀死

注意(重点):

  • 设置PTHREAD_CANCEL_DISABLE之后,线程接收到了取消请求,虽然线程没有被杀死,但是取消的请求仍处于挂起状态。当取消选项变为PTHREAD_CANCEL_ENABLE之后,线程将在下一个取消点上对所有挂起的取消请求进行处理
八、自定义线程取消点(pthread_testcancel)
#include <pthread.h>
void pthread_testcancel(void);
  • 功能:如果应用程序在很长的时间不会调用系统自带的取消点,那么可以调用此函数设置自己的取消点
  • 注意:在调用此函数时,如果有某个取消请求正处于挂起状态,而且取消并没有置为无效,那么线程就会被取消。但是,如果取消被置为无效,此函数调用就没有任何效果了

下图是POSIX.1定义的可选取消点:

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_exit_02

九、线程取消类型(pthread_setcanceltype)
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);

//返回值:若成功返回0;失败返回错误编号
  • 功能:上面介绍过当线程被取消时,只有当运行到取消点时线程才会被真正取消,但是还可以使用这个函数来设置线程是否运行到取消点才被取消

参数:

  • type:设置的新的取消类型(见下面)
  • oldtype:用来保存原先的取消类型

取消类型值:

  • PTHREAD_CANCEL_DEFERRED:(系统默认类型)只有线程到达一定的取消点,才会取消
  • PTHREAD_CANCEL_ASYNCHRONOUS:不论有没有到达取消点,立刻被取消。这种类型的取消称为“异步取消”
十、pthread_exit()参数的使用案例
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
void * thr_fn1(void *arg){
    printf("thread 1 returning\n");
    return((void *)1);
}
void *thr_fn2(void *arg){
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}
int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);//创建线程1
    if (err != 0)
        printf("can’t create thread 1\n");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);//创建线程2
    if (err != 0)
        printf("can’t create thread 2\n");

    err = pthread_join(tid1, &tret);//等待线程1
    if (err != 0)
        printf("can’t join with thread 1\n");
    printf("thread 1 exit code %ld\n", (long)tret);
    
    err = pthread_join(tid2, &tret);//等待线程2
    if (err != 0)
        printf("can’t join with thread 2\n");
    printf("thread 2 exit code %ld\n", (long)tret);
   
     exit(0);
}

演示结果

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_cancel_03

十一、pthread_exit()参数的不正确使用案例
#include <pthread.h>
#include<stdio.h>
#include<stdlib.h>
struct foo {
    int a, b, c, d;
};

void printfoo(const char *s, const struct foo *fp){
    printf("%s", s);
    printf(" structure at 0x%lx\n", (unsigned long)fp);
    printf(" foo.a = %d\n", fp->a);
    printf(" foo.b = %d\n", fp->b);
    printf(" foo.c = %d\n", fp->c);
    printf(" foo.d = %d\n", fp->d);
}

void * thr_fn1(void *arg){
    struct foo foo = {1, 2, 3, 4};
    printfoo("thread 1:\n", &foo);
    pthread_exit((void *)&foo);
}

void * thr_fn2(void *arg){
    printf("thread 2: ID is %lu\n", (unsigned long)pthread_self());
    pthread_exit((void *)0);
}

int main(void)
{
    int err;
    pthread_t tid1, tid2;
    struct foo *fp;

    err = pthread_create(&tid1, NULL, thr_fn1, NULL);//创建线程1
    if (err != 0)
        printf("can’t create thread 1\n");
    err = pthread_join(tid1, (void *)&fp);//等待线程1结束
    if (err != 0)
        printf("can’t join with thread 1\n");
    sleep(1);

    printf("parent starting second thread\n");
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);//创建线程2
    if (err != 0)
        printf("can’t create thread 2\n");
    sleep(1);
    
    printfoo("parent:\n", fp);
    exit(0);
}

运行结果:

  • 在Linux上面运行

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_线程的终止_04

  • 在Solaris上运行与Linux类似
  • 在Max OS X上运行,父进程尝试访问已退出的第一个线程传递给他的结构时,内存不再有效,会产生SIGSEGV信号并进程终止产生core文件
  • 在FresBSD运行时,父进程访问内存时,内存并没有覆盖,虽然线程已经退出了,但是内存依然是完整地

总结:虽然在FreeBSD可以运行,但是不同的平台效果不同,需要注意这一点

十二、线程取消选项演示案例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *thread_func(void* arg);

int main()
{
    pthread_t tid;
    int *thread_exit_code=malloc(sizeof(int));
    if(pthread_create(&tid,NULL,thread_func,NULL)!=0){
        fprintf(stdout,"pthread_create error\n");
        exit(EXIT_FAILURE);
    }

    //进程休眠1秒,然后取消子线程
    sleep(1);
    if(pthread_cancel(tid)!=0){
        fprintf(stdout,"pthread_cancel error\n");
        exit(EXIT_FAILURE);
    }

    printf("pthread_cancel filaed\n");
    
    //睡眠8秒之后取消线程失败了,因为线程已经退出了
    sleep(8);
    if(pthread_cancel(tid)!=0){
        fprintf(stdout,"pthread_cancel error\n");
        exit(EXIT_FAILURE);
    }
    
    printf("kill thread success\n");

    if(pthread_join(tid,(void*)&thread_exit_code)==0){
        printf("pthread_join success,exit_code is %d\n",(int)*thread_exit_code);
    }else{
        fprintf(stdout,"pthread_join error\n");
        exit(EXIT_FAILURE);
    }

    exit(0);
}

void *thread_func(void* arg)
{
    int exit_code,i;
    
    //进入之后,先设置自己为不可取消状态
    printf("I am thread,now my cancle type is disable\n");
    if(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL)!=0){
        fprintf(stdout,"pthread_setcancelstate error\n");
        exit_code=-1;
        pthread_exit(&exit_code);
    }
   

    for(i=1;i<=3;i++){
        sleep(1);
        printf("thread running (%d)\n",i);
    }

    //休眠3秒之后之后设置线程可以被取消
    printf("I am thread,now my cancle type is enable\n");
    if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL)!=0){
        fprintf(stdout,"pthread_setcancelstate error\n");
        exit_code=-1;
        pthread_exit(&exit_code);
    }
    
    printf("thread sleep....\n");
    sleep(20);

    pthread_exit(NULL);
}

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_exit_05

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_join_06

十三、演示案例(线程退出码的问题)
  • 下面的exit1.c程序运行了两个子线程,子线程退出的时候返回自己的退出码,但是这个退出码是在子线程的执行函数中定义的(存在于栈中),当我们的子线程执行函数执行完之后,退出码就释放了(栈释放),因此返回到main主函数的时候,我们pthread_join再去获取子线程的退出码就获取的是一个乱值,因此程序运行结果显示“return error”
//exit1.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *func(void *arg);

int main()
{
    int num=10;
    int *retValue=(int*)malloc(sizeof(int));
    pthread_t tid1,tid2;
    
    if(pthread_create(&tid1,NULL,func,(void*)&num)!=0){
        perror("thread 1 create error");
        exit(EXIT_FAILURE);
    }
    if(pthread_create(&tid2,NULL,func,(void*)&num)!=0){
        perror("thread 2 create error");
        exit(EXIT_FAILURE);
    } 

    if(pthread_join(tid1,(void**)&retValue)!=0){
        perror("pthread_join 1 error");
        exit(EXIT_FAILURE);
    }else{
        if((*retValue)==0){
            printf("Thread1 return success\n");
        }else{
            printf("Thread1 return error\n");
        }
    }
    if(pthread_join(tid2,(void**)&retValue)!=0){
        perror("pthread_join 2 error");
        exit(EXIT_FAILURE);
    }else{
        if((*retValue)==0){
            printf("Thread2 return success\n");
        }else{
            printf("Thread2 return error\n");
        }
    }

    //pthread_join会自动释放这个内存,如果此处还释放retValue,程序就会出错
    /*if(retValue){
        free(retValue);
        retValue=NULL;
    }*/
    
    exit(EXIT_SUCCESS);
}

void *func(void *arg)
{
    sleep(3);
    int success=0;
    int error=1;
    pthread_exit(&success);
}

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_cancel_07

程序改进

  •  我们对exit1.c程序进行了更改,将子线程的退出码定义为全局变量,这样在子线程的函数执行完之后,退出码也不会释放,我们的main函数在获取退出码的时候就不会获取乱值,因此打印“retur success”
//exit2.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int success=0;
int error=1;

void *func(void *arg);

int main()
{
    int num=10;
    int *retValue=(int*)malloc(sizeof(int));
    pthread_t tid1,tid2;
    
    if(pthread_create(&tid1,NULL,func,(void*)&num)!=0){
        perror("thread 1 create error");
        exit(EXIT_FAILURE);
    }
    if(pthread_create(&tid2,NULL,func,(void*)&num)!=0){
        perror("thread 2 create error");
        exit(EXIT_FAILURE);
    } 

    if(pthread_join(tid1,(void**)&retValue)!=0){
        perror("pthread_join 1 error");
        exit(EXIT_FAILURE);
    }else{
        if((*retValue)==0){
            printf("Thread1 return success\n");
        }else{
            printf("Thread1 return error\n");
        }
    }
    if(pthread_join(tid2,(void**)&retValue)!=0){
        perror("pthread_join 2 error");
        exit(EXIT_FAILURE);
    }else{
        if((*retValue)==0){
            printf("Thread2 return success\n");
        }else{
            printf("Thread2 return error\n");
        }
    }

    //pthread_join会自动释放这个内存,如果此处还释放retValue,程序就会出错
    /*if(retValue){
        free(retValue);
        retValue=NULL;
    }*/
    
    exit(EXIT_SUCCESS);
}

void *func(void *arg)
{
    sleep(3);
    pthread_exit(&success);
}

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_#include_08

十四、演示案例(线程退出码的问题)
  •  
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int success=0;
int error=1;

void *func(void *arg);

int main()
{
    int num=10;
    int *retValue=(int*)malloc(sizeof(int));
    pthread_t tid1,tid2;
    
    if(pthread_create(&tid1,NULL,func,(void*)&num)!=0){
        perror("thread 1 create error");
        exit(EXIT_FAILURE);
    }
    if(pthread_create(&tid2,NULL,func,(void*)&num)!=0){
        perror("thread 2 create error");
        exit(EXIT_FAILURE);
    } 

    if(pthread_join(tid1,(void**)&retValue)!=0){
        perror("pthread_join 1 error");
        exit(EXIT_FAILURE);
    }else{
        if((*retValue)==0){
            printf("Thread1 return success\n");
        }else{
            printf("Thread1 return error\n");
        }
    }
    if(pthread_join(tid2,(void**)&retValue)!=0){
        perror("pthread_join 2 error");
        exit(EXIT_FAILURE);
    }else{
        if((*retValue)==0){
            printf("Thread2 return success\n");
        }else{
            printf("Thread2 return error\n");
        }
    }

    //pthread_join会自动释放这个内存,如果此处还释放retValue,程序就会出错
    if(retValue){
        free(retValue);
        retValue=NULL;
    }
    
    exit(EXIT_SUCCESS);
}

void *func(void *arg)
{
    sleep(3);
    pthread_exit(&success);
}

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_线程的终止_09

解决办法一

  • 将代码中的释放代码注释掉,因为已经被释放了,所以不需要再次释放

 APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_exit_10

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_#include_11

解决办法二

  • 用申请的栈变量来保存子线程的退出码,栈不需要手动释放,在程序解决之后会自动释放
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int success=0;
int error=1;

void *func(void *arg);

int main()
{
    int num=10;
    int retValue;
    int *p=&retValue;
    pthread_t tid1,tid2;
    
    if(pthread_create(&tid1,NULL,func,(void*)&num)!=0){
        perror("thread 1 create error");
        exit(EXIT_FAILURE);
    }
    if(pthread_create(&tid2,NULL,func,(void*)&num)!=0){
        perror("thread 2 create error");
        exit(EXIT_FAILURE);
    } 

    if(pthread_join(tid1,(void**)&p)!=0){
        perror("pthread_join 1 error");
        exit(EXIT_FAILURE);
    }else{
        if((retValue)==0){
            printf("Thread1 return success\n");
        }else{
            printf("Thread1 return error\n");
        }
    }
    if(pthread_join(tid2,(void**)&p)!=0){
        perror("pthread_join 2 error");
        exit(EXIT_FAILURE);
    }else{
        if((retValue)==0){
            printf("Thread2 return success\n");
        }else{
            printf("Thread2 return error\n");
        }
    }

    exit(EXIT_SUCCESS);
}

void *func(void *arg)
{
    sleep(3);
    pthread_exit(&success);
}

APUE编程:54---线程处理(线程的退出、等待、取消、取消点:pthread_exit,pthread_join,pthread_cancel、pthread_setcancelstate)_pthread_exit_12