一、线程与进程的关系
- ①如果进程中的任意线程调用了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可以运行,但是不同的平台效果不同,需要注意这一点