三、Linux下线程的终止

线程的终止

(一)线程终止的三种方式

主动方式

1.线程的执行函数调用return语句

从线程主函数中return返回。这种方法对主控线程不适用,从main函数return相当于调用exit,将会退出整个进程
线程的返回:return (void*)0;

2.调用pthread_exit(void *retval)

原型:void pthread_exit(void *retval)
用法:#include <pthread.h>
功能:使用函数pthread_exit退出线程,这是线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源
说明:retval:pthread_exit()调用线程的返回值,可由其他函数如pthread_join来检索获取。

以下是主动退出线程的两种主动方式:

void *thread_function(void *arg)
{
        RaceArg *Arg=(RaceArg*)arg;
        int i;
        int time=Arg->time;
        for(i=Arg->start;i<=Arg->end;i++)
        {
                printf("%7s Run=%d\r\n",Arg->name,i);
                usleep(time);//休眠us时间
        }
        return (void*)0;
        //用pthread_exit(0)来调用线程的返回值,用来退出线程,但是退出线程所占用的资源不会随着线程的终止而得到释放
        //pthread_exit(0);
}

注意:pthread_create和pthread_exit函数的无类型指针参数能传递的数值可以不止一个,该指针可以传递包含更复杂信息
的结构的地址,但是注意这个结构所使用的内存在调用者完成调用以后必须保证仍是有效的,否则就会出现非法内存访问。
例如,在调用线程(如main函数)的栈上分配了一个结构体,那么其他的线程在使用这个结构体时,结构体中的内容可能已经
改变了。

被动方式

3.pthread_cancel(pthread_t tid)

线程可以被同一进程的其他线程取消,其他线程调用pthread_cancel(pthread_t tid)。
pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。

原型:int pthread_cancel(pthread_t thread)
用法:#include <pthread.h>
功能:杀死(取消)线程,其作用对应进程中kill()函数。调用此函数来请求取消同一进程中的其他线程。
说明:向thread发送一个取消执行的请求。如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
注意:如果线程只是从当前线程返回或者调用pthread_exit正常终止,rval_ptr将包含返回码。但是如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。

注意:被取消的线程,退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。
可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)。
因此当我们对一个已经被取消的线程使用pthread_join回收时,得到的返回值为-1。

(二)pthread_ join()函数详解

pthread_join函数
函数pthread_join用来等待一个线程的结束,线程间同步的操作。
头文件 : #include <pthread.h>
函数定义: int pthread_join(pthread_t thread, void **retval);
描述 :pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :thread: 线程标识符,即线程ID,标识唯一线程。 retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号。

当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。这里有三点需要注意:

  • 被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的空间,比如 malloc() 分配的空间。
  • 一个线程只能被一个线程所连接。
  • 被连接的线程必须是非分离的,否则连接会出错。

pthread_join()的两种作用:

  • 用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。
  • 对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。

a. 例程1

#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
 
typedef struct
{
	int		d1;
	int		d2;
}Arg;
 
void* th_fn(void *arg)
{
	Arg *r = (Arg*)arg;
 
	//return (void*)(r->d1 + r->d2);
	return (void*)r;
}
 
int main(void)
{
	int err;
	pthread_t th;
	Arg r = {20, 50};
 
	if((err = pthread_create(&th, NULL,th_fn, (void*)&r)) != 0)
	{
		perror("pthread_create error");
	}
    /*存放地址的变量叫指针变量 
    int *result   定义指向整形数据的指针变量
	result相当于存放的是地址值,(int)result:将此地址的值转换成int型
	一般定义指针变量 
	int a=5;
	int *p;
	p=&a;
	*/
	/*
	int *result;
	pthread_join(th, (void**)&result);//第二个参数获得子线程的返回结果
	printf("result is %d\n", (int)result);//这里做了强制转换
	*/

	/*
	定义一个变量result,即申请了个内存地址。
	&result对该变量取出变量的存储地址
	将要返回的数据,返回值变量存储的地址上
	直接result就可以输出返回的值
	*/
	/*
	int result;
	pthread_join(th, void(*)&result);
	printf("result is %d\n", result);//这里就不需要强制转换
	*/
    /*
	定义整形指针变量
	将要返回的值写入指针变量的地址中
	强制转换成结构体指针输出
	*/
	int *result;
	pthread_join(th, (void**)&result);
	printf("result is %d\n",((Arg*)result)->d1 + ((Arg*)result)->d2);
	
	return 0;
}

b. 例程2

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void *thread_function(void* arg);
typedef struct{
        char name[20];
        int  time;
        int  start;
        int  end;
}RaceArg;
int main()
{
        int result;
        //线程返回的值
        void* thread_result;
        //定义两个线程标示符指针 乌龟和兔子
        pthread_t rabbit,tortoise;
        //定义兔子的初始结构体
        RaceArg rabbit_arg={"Rabbit", (int)(drand48()*10000000),0,100};
        //定义乌龟的初始结构体
        RaceArg tortise_arg={"Tortise",(int)(drand48()*10000000),10,90};
        //使用pthread_create创建两个线程,并传入距离值50m
        result=pthread_create(&rabbit,NULL,thread_function,(void*)&rabbit_arg);
        if(result!=0)
        {
                perror("pthrad creat fail!\r\n");
                exit(EXIT_FAILURE);
        }
        result=pthread_create(&tortoise,NULL,thread_function,(void*)&tortise_arg);
        if(result!=0)
        {
                perror("pthrad creat fail!\r\n");
                exit(EXIT_FAILURE);
        }
        //主控线程调用pthreat_join(),自己会阻塞
        //直到rabbit和tortoise线程结束,方可运行
        pthread_join(rabbit,&thread_result);
        //返回兔子比赛的距离值
        printf("rabbit run distance=%d\r\n",(int*)thread_result);
        pthread_join(tortoise,&thread_result);
        //返回乌龟比赛的距离值
        printf("tortise run distance=%d\r\n",(int*)thread_result);

        printf("Control thread_id=%lx\r\n",pthread_self());
        printf("Finish\r\n");
        return 0;
}
void *thread_function(void *arg)
{
        RaceArg *Arg=(RaceArg*)arg;
        int i;
        int time=Arg->time;
        for(i=Arg->start;i<=Arg->end;i++)
        {
                printf("%7s(%lx) Run=%d\r\n",Arg->name,pthread_self(),i);
                usleep(time);//休眠us时间
        }
        //返回相关的参数值
        return (void*)(Arg->end-Arg->start);
}

(三)线程回收

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

  • 线程的分离属性:分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束时,其所属的资源可以回收。
    一个没有被分离的线程在终止时会保留它的虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被称为“僵尸线程”。
    创建线程时默认是非分离的。
  • 如果线程具有分离属性,线程终止时会被立刻回收,回收将释放掉所有在线程终止时未释放的系统资源和进程资源,
    包括保存线程返回值的内存空间、堆栈、保存寄存器的内存空间等。
  • 终止被分离的线程会释放所有的系统资源,但是你必须释放由该线程占有的程序资源。由malloc或者mmap分配的内存可以在任何时候由任何线程释放,条件变量、互斥量、信号灯可以由任何线程销毁,只要他们被解锁了或者没有线程等待。但是只有互斥量的主人才能解锁它,所以在线程终止前,你需要解锁互斥量。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值