2016-08-03 16:38:28 bingyu880101 阅读数 1107
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5046 人正在学习 去看看 朱有鹏

关键词:线程资源释放, pthread_join()

1, 每个线程有自己的一组寄存器,自己的栈空间, 自己的errno变量;

2,pthread_create线程创建并不能保证哪个线程先运行,新线程还是调用线程。

3,线程正常退出的方式有三种: 线程函数直接return, 被同一进程中的其他线程调用pthread_cancel取消,线程调用pthread_exit 退出,(线程运行异常退出)。

4,不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题,https://www.ibm.com/developerworks/cn/linux/thread/posix_threadapi/part4/ 一文中介绍了pthread_cleanup_push()/pthread_cleanup_pop()解决资源释放问题,因为没有用到,不写在这里,对于异常退出的情况,注意在每个退出的点上都加上资源,尤其是锁资源的释放。

5,对于线程正常退出的资源释放,根据线程的属性,有两种方式: 对于属性为joinable的(默认),线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放对于属性为detached分离状态的,线程在结束运行时自行释放所占用的内存资源。

6,把线程置为detached属性有两种方式,一种是在线程创建时设置属性pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);另一种是调用pthread_detach()使线程成为detached,就可以自动释放资源了。

7,对于属性为joinable的线程,可以通过调用pthread_detach()将其属性改变为detached, 但是属性为detached的线程,不能改回joinable属性, 也就是不能调用pthread_join()释放资源。

8,如果线程pthread_detach()执行之后,对线程请求pthread_join()将返回错误,如果线程已经被调用pthread_join()后,再调用pthread_detach()就不会有任何效果。

9,一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收

10,pthread_join()函数:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

2015-08-07 16:44:29 yangbingzhou 阅读数 3470
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5046 人正在学习 去看看 朱有鹏

             在Linux中,默认情况下是在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,但是可以设置Threads attributes来设置当一个线程结束时,直接回收此线程所占用的系统资源,详细资料查看Threads attributes。

其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统调用copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。 copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在copy后的进程中便能体现出来。
pthread_join的应用
pthread_join使一个线程等待另一个线程结束。
代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。通过调用pthread_self()函数可以获得自身的线程号。

2使用范例编辑

一个线程的结束有两种途径,一种是象我们下面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。另外需要说明的是,一个线程不能被多个线程等待,也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返回ESRCH 错误。
在Linux中,默认情况下是在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,但是可以设置Threads attributes来设置当一个线程结束时,直接回收此线程所占用的系统资源,详细资料查看Threads attributes。
范例:
//signaltest.c
  // 子线程阻塞,等待信号,然后输出字符串
  // 主线程从键盘录入字符,给子线程发信号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<pthread.h>
#include<time.h>
pthread_ttid; sigset_tset;
voidmyfunc()
{
 printf("hello\n");
}
staticvoid*mythread(void*p)
{
 intsignum;
 while(1){
 sigwait(&set,&signum);
 if(SIGUSR1==signum)
 myfunc();
 if(SIGUSR2==signum)
 {
 printf("Iwillsleep2secondandexit\n");
 sleep(2);
 break;
 }
}
}
intmain()
{
chartmp;
void*status;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
sigaddset(&set,SIGUSR2);
sigprocmask(SIG_SETMASK,&set,NULL);
pthread_create(&tid,NULL,mythread,NULL);
while(1)
{
printf(":");
scanf("%c",&tmp);
if('a'==tmp)
{
pthread_kill(tid,SIGUSR1);//发送SIGUSR1,打印字符串。
}
elseif('q'==tmp)
{
//发出SIGUSR2信号,让线程退出,如果发送SIGKILL,线程将直接退出。
pthread_kill(tid,SIGUSR2);
//等待线程tid执行完毕,这里阻塞。
pthread_join(tid,&status);
printf("finish\n");
break;
}
else
continue;
}
return0;
}
运行结果:
// 如果输入a,子线程打印"hello",主程序继续等待输入;
// 如果输入q,主程序等待子程序结束。子线程打印"I will sleep 2 second and exit",并延时两秒后结束。主线程随之打印"finish",程序结束。
在前面我们提到,可以通过pthread_join()函数来使主线程阻塞等待其他线程退出,这样主线程可以清理其他线程的环境。但是还有一些线程,更喜欢自己来清理退出的状态,他们也不愿意主线程调用pthread_join来等待他们。我们将这一类线程的属性称为detached。如果我们在调用pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线程采用默认的属性,也就是joinable。如果需要将属性设置为detached,则参考下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void*start_run(void*arg)
{
//dosomework
}
 
intmain()
{
pthread_tthread_id;
pthread_attr_tattr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(&thread_id,&attr,start_run,NULL);
pthread_attr_destroy(&attr);
sleep(5);
exit(0);
}
在线程设置为joinable后,可以调用pthread_detach()使之成为detached。但是相反的操作则不可以。还有,如果线程已经调用pthread_join()后,则再调用pthread_detach()则不会有任何效果。
2010-11-18 22:16:00 summer_liuwei 阅读数 703
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5046 人正在学习 去看看 朱有鹏

     一般来说,对一段运行代码进行加锁然后解锁,如下所示:

  pthread_mutex_lock(&mutex);

  //运行代码;

  pthread_mutex_unlock(&mutex);

  如果在运行代码这块发生错误,有异常,导致这个线程异常退出,那么怎么办,pthread_unlock没有得到调用,那么这个锁资源没有解锁。可以用下面的方法修改。

  pthread_cleanup_push(pthread_mutex_unlock, (void *) &mutex);

  pthread_mutex_lock(&mutex);

  /* do some work */

  pthread_mutex_unlock(&mutex);

  pthread_cleanup_pop(0);

  这样假如运行代码发生错误时没有调用到解锁,pthread_cleanup_up会自动来调用,参数为0表示不执行push进来的函数。

  但是如果是异常错误的话,这个参数并不影响异常终止时清理函数的执行。

  必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()扩展函数,功能与以下代码段相当:

  { int oldtype;

  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

  pthread_cleanup_push(routine, arg);

  ...

  pthread_cleanup_pop(execute);

  pthread_setcanceltype(oldtype, NULL);

  }

  +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

  设置退出类型pthread_setcanceltype

  #include <pthread.h>

  int pthread_setcanceltype(int type, int *oldtype);

  返回值:函数成功返回0。任何其他返回值都表示错误。

  将线程退出类型设置为延迟类型或异步类型。参数type的取值为PTHREAD_CANCEL_DEFERRED或PTHREAD_CANCEL_ASYNCHRONOUS。

  当一个线程被创建后,缺省值是延迟类型。在异步方式下,线程可以在执行的任何时候被退出。

2019-04-19 14:57:57 qq_42169059 阅读数 85
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5046 人正在学习 去看看 朱有鹏

线程的清理函数

一、 清理函数的使用

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己 所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。
最经常出现的情形是资源独占锁的使用:线程为了访问临界共享资源而为其加上锁,但在访问过程中该线程被外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。 (比如子线程malloc的空间,等到子线程退出,这段空间并没有释放)。
在POSIX线程API中提供了控制清理函数的函数有两个,pthread_cleanup_push()与pthread_cleanup_pop()函数对用于自动释放资源。pthread_cleanup_push(), 用来把清理函数压入栈中, pthread_cleanup_pop(), 用来把栈中的函数弹出来。
从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:

函数原型
#include <pthread.h>
void pthread_cleanup_push(void (* rtn)(void *), void *arg);//注册清理函数,压栈
void pthread_cleanup_pop(int execute); //清理函数,出栈
函数参数:
rtn:清理函数指针
arg:调用清理函数传递的参数
execute:值 1 时,执行线程清理函数;值 0 时,不执行线程清理函数
返回值:成功,返回 0;否则,返回错误编号
注意:此两个函数是成对出现的,少一个会导致编译不通过。

a. 线程可以建立多个清理处理程序,处理程序在栈中记录,所以执行顺序与注册时顺序相反。
注意该处理函数是被放在栈区,所以先进栈的后出栈。,也就是说先进栈的函数,最后被调用。
b. 若excute参数设置为0,清理函数将不会被调用。无论是否调用pthread_cleanup_pop函数都将删除上次pthread_cleanup_push调用建立的清理处理程序。

c. 当有以下三种操作的时候会调用清理函数:

  • 调用pthread_exit()函数,会响应清理处理函数。
  • 用非零的函数调用pthread_cleanup_pop()函数。
  • 响应取消请求。

以下为另一版本调用线程清理函数的解释,意义是一样的。

  • 线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止。
  • 线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消。
  • 线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0。

注意:如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,是不会执行清理函数的如同进程可以调用atexit函数安排在他退出时需要调用的函数一样,进程也可以安排在他退出时调用的函数。这些清理函数记录在栈中,所以他们执行的顺序和注册的顺序是相反的。

二、 清理函数的程序

1.案例1:用非零的函数调用pthread_cleanup_pop()函数。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//定义线程清理函数
void * thread_clean(void* arg)
{
        char *s=(char*)arg;
        printf("clean_fun:%s\r\n",s);
}
//定义线程1函数
void *thread_fun1(void *arg)
{
        int execute=(int)arg;
        //注册清理函数,压栈
        pthread_cleanup_push(&thread_clean,"first clean");
        pthread_cleanup_push(&thread_clean,"second clean");

        printf("thread1(%lx)running\r\n",pthread_self());
        //清理函数,出栈
        pthread_cleanup_pop(execute);
        pthread_cleanup_pop(execute);

        return (void*)0;
}
//定义线程2函数
void *thread_fun2(void *arg)
{
        int execute=(int)arg;
		//注册清理函数,压栈
        pthread_cleanup_push(&thread_clean,"first clean");
        pthread_cleanup_push(&thread_clean,"second clean");

        printf("thread2(%lx)running\r\n",pthread_self());
		//清理函数,出栈
        pthread_cleanup_pop(execute);
        pthread_cleanup_pop(execute);
        return (void*)0;
}
int main(void)
{
         int result;
        pthread_t thread1,thread2;
        //新建两个线程,且传入的参数配置如下
        if((result=pthread_create(&thread1,NULL,thread_fun1,(void*)1))!=0)
        //if((result=pthread_create(&thread1,NULL,thread_fun1,(void*)0))!=0)//线程1传入的参数为0
        {
                perror("thread1 create error!\n\r");
        }
        //阻塞当前任务1,直至任务完成
        pthread_join(thread1,NULL);
        printf("thread1(%lx) finish\n\r",thread1);
        if((result=pthread_create(&thread2,NULL,thread_fun2,(void*)1))!=0)
        {
                perror("thread2 create error!\n\r");
        }
        //阻塞当前任务2,直至任务完成
        pthread_join(thread2,NULL);
        printf("thread2(%lx) finish\r\n",thread2);
        printf("Control thread(%lx) finish\r\n",pthread_self());

        return 0;
}                                 

a. 当线程1传入的参数为1时,pthread_cleanup_pop()函数的execute参数为1,执行线程清理函数。执行结果如下图:
在这里插入图片描述
b.当线程1传入的参数为0时,pthread_cleanup_pop()函数的execute参数为0,执行线程清理函数。执行结果如下图:
在这里插入图片描述
由于线程1传入的参数为0时,pthread_cleanup_pop()函数的execute参数为0,则清理函数将不会执行。无论是否调用pthread_cleanup_pop函数都将删除上次pthread_cleanup_push调用建立的清理处理程序。

2. 案例2:调用pthread_exit()函数,响应清理处理函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//定义线程清理函数
void * thread_clean(void* arg)
{
        char *s=(char*)arg;
        printf("clean_fun:%s\r\n",s);
}
//定义线程1函数
void *thread_fun1(void *arg)
{
        int execute=(int)arg;
        //注册清理函数,压栈
        pthread_cleanup_push(&thread_clean,"first clean");
        pthread_cleanup_push(&thread_clean,"second clean");

        printf("thread1(%lx)running\r\n",pthread_self());
        //清理函数出栈前,调用线程退出函数
        pthread_exit((void*)0);
        //若在清理函数出栈前,调用return
        //return (void*)0;
        
        //清理函数,出栈
        pthread_cleanup_pop(execute);
        pthread_cleanup_pop(execute);

        return (void*)0;
}
//定义线程2函数
void *thread_fun2(void *arg)
{
        int execute=(int)arg;
		//注册清理函数,压栈
        pthread_cleanup_push(&thread_clean,"first clean");
        pthread_cleanup_push(&thread_clean,"second clean");

        printf("thread2(%lx)running\r\n",pthread_self());
		//清理函数,出栈
        pthread_cleanup_pop(execute);
        pthread_cleanup_pop(execute);
        return (void*)0;
}
int main(void)
{
         int result;
        pthread_t thread1,thread2;
        //新建两个线程,且传入的参数配置如下
        if((result=pthread_create(&thread1,NULL,thread_fun1,(void*)1))!=0)
        //if((result=pthread_create(&thread1,NULL,thread_fun1,(void*)0))!=0)//线程1传入的参数为0
        {
                perror("thread1 create error!\n\r");
        }
        //阻塞当前任务1,直至任务完成
        pthread_join(thread1,NULL);
        printf("thread1(%lx) finish\n\r",thread1);
        if((result=pthread_create(&thread2,NULL,thread_fun2,(void*)1))!=0)
        {
                perror("thread2 create error!\n\r");
        }
        //阻塞当前任务2,直至任务完成
        pthread_join(thread2,NULL);
        printf("thread2(%lx) finish\r\n",thread2);
        printf("Control thread(%lx) finish\r\n",pthread_self());

        return 0;
}    

以上案例在线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止。pthread_cleanup_pop该函数的参数不管是0还是1,都将仍然调用了线程清理函数。执行的结果如下图:
在这里插入图片描述
分析:pthread_cleanup_pop()中参数是非0时会调用清理处理函数,当参数是0的时候,pthread_cleanup_pop该函数的参数在pthread_exit()退出前调用无论是0还是非0,都会响应清理处理函数,但是pthread_exit()在pthread_cleanup_pop函数之后调用,pthread_cleanup_pop函数的参数是0的话,清理函数已经退出,所以就不会再调用到清理处理函数了。
3. 案例3:调用return函数
将案例2的线程1,在使用pthread_cleanup_pop()函数前,使用return语句返回,执行结果如下:
在这里插入图片描述
分析:虽然在线程1中也调用了线程清理处理程序,但是由于在两个函数中间是通过return返回到启动例程当中,所以线程清理处理程序并未执行。调用return语句,线程已经退出了,再调用pthread_cleanup_pop()函数,将不会执行清理函数。

4. 案例4:响应取消请求

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

//定义清理函数
void clean(void *arg)
{
		printf("%s\n",(char *)arg);
}
//定义线程
void *thread(void *arg)
{
        printf("thread start:\n");
		
        pthread_cleanup_push(clean,"first handler");
        pthread_cleanup_push(clean,"second handler");
		
		//设置异步取消。使取消动作不必等到下个取消点
        pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
        while(1);//等待主线程取消
		//在线程取消后使用清理函数,参数0或者1都将执行清理函数
        pthread_cleanup_pop(0);
        pthread_cleanup_pop(0);
        return ((void *)0);
}
int main(void)
{
        int result;
		pthread_t tid;
        void *res;

        if((result=pthread_create(&tid,NULL,thread,(void *)0))!=0)
		{
			perror("thread create error!!\r\n");
		}
        //睡眠一秒好让子进程先运行(我们期望在发送取消新线程时,线程已经在运行)
		sleep(1);          
        pthread_cancel(tid);
        pthread_join(tid,&res);

        if(res==PTHREAD_CANCELED)
		{
            printf("thread exit code:%s\n","PTHREAD_CANCELED");
        }
        exit(0);
}

当新线程接收到取消信号时,因为已经设置了异步取消,所以线程会立刻退出。输出结果验证了当线程响应取消时会调用清理函数,并且返回值为PTHREAD_CANCELED。
在这里插入图片描述
分析:当新线程接收到取消信号时,因为已经设置了异步取消,所以线程会立刻退出。再调用pthread_cleanup_pop()函数,不管此函数的参数是0还是1,因为响应了取消请求,都将执行清理函数。但取消请求在pthread_cleanup_pop()函数之后,参数0会执行清除函数。1则不执行清除函数。

三、 清理函数触发总结

a. 调用pthread_exit()函数

  • 如果在pthread_exit前执行了pthread_cleanup_pop(0), 清理函数不会执行

  • 如果在pthread_exit前执行了pthread_cleanup_pop(1), 清理函数会执行

  • pthread_exit在pthread_cleanup_pop函数前被执行, 清理函数一定会执行

b. 响应取消请求

  • 线程取消前执行了pthread_cleanup_pop(0), 就不会执行清理函数
  • 线程取消前执行了pthread_cleanup_pop(1), 清理函数会执行
  • 线程取消后执行了pthread_cleanup_pop(0或者), 清理函数都会执行

c. 调用return()

  • 如果在return前执行了pthread_cleanup_pop(0), 清理函数不会执行

  • 如果在return前执行了pthread_cleanup_pop(1), 清理函数会执行

  • return在pthread_cleanup_pop函数前被执行, 清理函数一定不会执行

2015-08-26 20:02:20 yangzhao0001 阅读数 3647
  • linux线程全解-linux应用编程和网络编程第7部分

    本课程讲解linux中线程,首先使用多进程解决上个课程中提出的并发式读取按键和鼠标的任务,然后引出多线程并讲解多线程的优势,后详细讲了多线程的同步技术。学习本课程的目的是学会在linux应用编程中使用多线程技术。

    5046 人正在学习 去看看 朱有鹏

多线程退出有三种方式:
(1)执行完成后隐式退出;

(2)由线程本身显示调用pthread_exit 函数退出;
    pthread_exit (void * retval) ; 

(3)被其他线程用pthread_cance函数终止:
    pthread_cance (pthread_t thread) ; 


用event来实现。

在子线程中,在循环内检测event。
while(!e.is_active())
{
  ...
}
当退出循环体的时候,自然return返回。这样子线程会优雅的结束。

注意:选用非等待的检测函数。


pthread 线程有两种状态,joinable(非分离)状态和detachable(分离)状态,默认为joinable。

  joinable:当线程函数自己返回退出或pthread_exit时都不会释放线程所用资源,包括栈,线程描述符等(有人说有8k多,未经验证)。

  detachable:线程结束时会自动释放资源。

Linux man page said:

When a joinable thread terminates, its memory resources (thread descriptor and stack) are not deallocated until another thread performs pthread_join on it. Therefore, pthread_join must be called  once  for each joinable thread created to avoid memory leaks.

因此,joinable 线程执行完后不使用pthread_join的话就会造成内存泄漏。

解决办法:

1.// 创建线程前设置 PTHREAD_CREATE_DETACHED 属性

pthread_attr_t attr;
pthread_t thread;
pthread_attr_init (&attr);
pthread_attr_setdetachstat(&attr, PTHREAD_CREATE_DETACHED);
pthread_create (&thread, &attr, &thread_function, NULL);
pthread_attr_destroy (&attr);


2.当线程为joinable时,使用pthread_join来获取线程返回值,并释放资源。

3.当线程为joinable时,也可在线程中调用 pthread_detach(pthread_self());来分离自己。


=========================================================================


/********************************** pthread_exit.c **************************************/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>

void exe_exit1(void *arg)
{
    printf("我是线程退出时要执行的程序1!");
    printf("传递给我的参数是:%s\n",(char *)arg);
}

void exe_exit2(void *arg)
{
    printf("我是线程退出时要执行的程序2!");
    printf("传递给我的参数是:%s\n",(char *)arg);
}

void *func()
{
    int ret;
    
    ret=pthread_detach(pthread_self());/*使本线程成为分离状态的线程*/
    if(ret==0)
        printf("pthread_detach调用成功!\n");
    else
        printf("pthread_detach调用出错,错误编号:%d",ret,errno);
        
    printf("我是线程,已经开始启动!\n");
    pthread_cleanup_push(exe_exit2,"给exe_exit2的参数");
    pthread_cleanup_push(exe_exit1,"给exe_exit1的参数");
    printf("pthread_cleanup_push设置完成。\n");
    printf("我是线程,即将退出。\n");
    pthread_exit((void *)0);
    pthread_cleanup_pop(0);/*下面两行不能少,否则会编译不通过,提示语法错误*/
    pthread_cleanup_pop(0);
//    printf("pthread_cleanup_pop设置完成。\n");
}

int main()
{
    int ret;
    pthread_t tid;
    
    pthread_create(&tid,NULL,func,NULL);
    
    sleep(2);
    exit(0);
}

编译: gcc -o pthread_exit -lpthread pthread_exit.c
执行: ./pthread_exit

///////////////////////////////// 运行结果 //////////////////////////////////
pthread_detach调用成功!
我是线程,已经开始启动!
pthread_cleanup_push设置完成。
我是线程,即将退出。
我是线程退出时要执行的程序1!传递给我的参数是:给exe_exit1的参数
我是线程退出时要执行的程序2!传递给我的参数是:给exe_exit2的参数

几个需要说明的地方:

1、pthread_cleanup_pop的参数是0的话,表示不执行线程清理处理程序(但并不妨碍把栈顶存放的那个处理程序的记录弹出,只是不执行而已),即这里的exe_exit1和exe_exit2。因此,如果程序中的这一句pthread_exit((void *)0);移到func函数结尾的话,exe_exit1和exe_exit2函数将不会得到执行,因为之前的两个pthread_cleanup_pop函数已经把存储的处理程序的记录给清空了。再执行到pthread_exit((void *)0);的时候栈中已经没有待执行的处理函数了。

2、虽然本程序中的pthread_cleanup_pop函数的参数是0,这两句本身不会触发线程清理处理程序(触发线程清理处理程序的任务是由pthread_exit来完成的)。但是这两句不能去掉,可以试一下把这两句注释掉,再编译的话就会出现错误。原因估计应该是pthread_cleanup_pop必须要和pthread_cleanup_push成对出现吧。

3、我一开始把pthread_detach函数放在了main函数中,结果运行时总是提示Segmentation fault(段错误)。后来在网上搜索后,改为把pthread_detach函数在func函数中调用,问题解决。 


没有更多推荐了,返回首页