UNIX(编程-线程处理):04---线程的终止与取消(pthread_exit,pthread_join,pthread_cancel)

一、线程与进程的关系

  • ①如果进程中的任意线程调用了exit、_Exit、_exit,那么整个进程就会终止
  • ②如果默认的动作是终止进程,那么,发送到某个线程的信号就会终止整个进程

二、线程的终止方式

线程有三种终止方式。(下面三种方式是在:正常不终止整个进程的情况下,终止线程并且停止它的控制流)

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

三、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);
  • 功能:用来等待参数1指定的线程结束
  • 此函数会阻塞,直到指定的线程调用pthread_exit、从启动线程中返回、或者被取消,此函数才返回

参数:

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

返回值:

  • 成功:返回0
  • 失败:返回错误编号

线程分离(重要):

  • 可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复
  • 如果线程已处于分离状态(例如调用pthread_detach函数),pthread_join调用会失败,返回EINVAL,尽管这种行为是与具体实现相关的

五、pthread_exit()参数与pthread_join()参数的注意事项

注意事项(重点)

  • pthread_create 和 pthread_exit 函数的无类型指针参数可以传递的值不止一个,这个指针可以传递包含复杂信息的结构体地址,但是注意,这个结构所使用的内存在调用者完成调用以后必须仍然是有效的
  • 例如,在调用线程的栈上分配了该结构,那么其他的线程在使用这个结构时内存内容可能已经改变了
  • 又如,线程在自己的栈上分配了一个结构,然后把指向这个结构的指针传给 pthread_exit ,那么调用 pthread_join 的线程试图使用该结构时,这个栈有可能已经被撤销,这块内存也另做他用

六、pthread_cancel函数

#include <pthread.h>
int pthread_cancel(pthread_t tid);
  • 功能:线程可以通过pthread_cancel来请求取消同一进程中的其它线程

参数:

  • 需要取消的线程ID

返回值:

  • 成功返回0;否则返回错误编号

注意事项:

  • 默认情况下,pthread_cancel函数会使得由tid 标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数
  • 线程可以选择忽略取消或者控制如何被取消
  • pthread_cancel 并不等待线程终止,它仅仅提出请求

六、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);
}

演示结果

七、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上面运行

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

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

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/89305478