一、线程退出
1、pthread_kill
(1)引用
#include <pthread.h>
#include<signal.h>
(2)函数原型
int pthread_kill(pthread_t thread, int sig);
(3)参数
- thread:线程ID
- sig:sig信号
(4)返回值
-
0:调用成功。
-
ESRCH:线程不存在。。
- EINVAL:信号不合法
(4)作用
向指定的线程传递sig信号。
(5)注意
(6)程序实例
/******************************* pthread_kill.c *******************************/
#include<stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
void *func1()/*1秒钟之后退出*/
{
sleep(1);
printf("线程1(ID:0x%x)退出。
",(unsigned int)pthread_self());
pthread_exit((void *)0);
}
void *func2()/*5秒钟之后退出*/
{
sleep(5);
printf("线程2(ID:0x%x)退出。
",(unsigned int)pthread_self());
pthread_exit((void *)0);
}
void test_pthread(pthread_t tid) /*pthread_kill的返回值:成功(0) 线程不存在(ESRCH) 信号不合法(EINVAL)*/
{
int pthread_kill_err;
pthread_kill_err = pthread_kill(tid,0);
if(pthread_kill_err == ESRCH)
printf("ID为0x%x的线程不存在或者已经退出。
",(unsigned int)tid);
else if(pthread_kill_err == EINVAL)
printf("发送信号非法。
");
else
printf("ID为0x%x的线程目前仍然存活。
",(unsigned int)tid);
}
int main()
{
int ret;
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,func1,NULL);
pthread_create(&tid2,NULL,func2,NULL);
sleep(3);/*创建两个进程3秒钟之后,分别测试一下它们是否还活着*/
test_pthread(tid1);/*测试ID为tid1的线程是否存在*/
test_pthread(tid2);/*测试ID为tid2的线程是否存在*/
exit(0);
}
//kill给线程发信号
#include <pthread.h>
#include <stdio.h>
#include <sys/signal.h>
#define NUMTHREADS 3
void sighand(int signo);
void *threadfunc(void *parm)
{
pthread_t tid = pthread_self();
int rc;
printf("Thread %u entered/n", tid);
rc = sleep(30); /* 若有信号中断则返回剩余秒数 */
printf("Thread %u did not get expected results! rc=%d/n", tid, rc);
return NULL;
}
void *threadmasked(void *parm)
{
pthread_t tid = pthread_self();
sigset_t mask;
int rc;
printf("Masked thread %lu entered/n", tid);
sigfillset(&mask); /* 将所有信号加入mask信号集 */
/* 向当前的信号掩码中添加mask信号集 */
rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);
if (rc != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return NULL;
}
rc = sleep(15);
if (rc != 0)
{
printf("Masked thread %lu did not get expected results! rc=%d /n", tid, rc);
return NULL;
}
printf("Masked thread %lu completed masked work/n", tid);
return NULL;
}
int main(int argc, char **argv)
{
int rc;
int i;
struct sigaction actions;
pthread_t threads[NUMTHREADS];
pthread_t maskedthreads[NUMTHREADS];
printf("Enter Testcase - %s/n", argv[0]);
printf("Set up the alarm handler for the process/n");
memset(&actions, 0, sizeof(actions));
sigemptyset(&actions.sa_mask); /* 将参数set信号集初始化并清空 */
actions.sa_flags = 0;
actions.sa_handler = sighand;
/* 设置SIGALRM的处理函数 */
rc = sigaction(SIGALRM,&actions,NULL);
printf("Create masked and unmasked threads/n");
for(i=0; i<NUMTHREADS; ++i)
{
rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
if (rc != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return -1;
}
rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);
if (rc != 0)
{
printf("%d, %s/n", rc, strerror(rc));
return -1;
}
}
sleep(3);
printf("Send a signal to masked and unmasked threads/n");
/* 向线程发送SIGALRM信号 */
for(i=0; i<NUMTHREADS; ++i)
{
rc = pthread_kill(threads[i], SIGALRM);
rc = pthread_kill(maskedthreads[i], SIGALRM);
}
printf("Wait for masked and unmasked threads to complete/n");
for(i=0; i<NUMTHREADS; ++i) {
rc = pthread_join(threads[i], NULL);
rc = pthread_join(maskedthreads[i], NULL);
}
printf("Main completed/n");
return 0;
}
void sighand(int signo)
{
pthread_t tid = pthread_self();
printf("Thread %lu in signal handler/n", tid);
return;
}
2、pthread_exit
void pthread_exit(void* retval);
(2)参数
pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。
(3)作用
线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。
(4)用法
- 在线程中调用pthread_exit退出线程,在主线程中嗲用pthread_join函数阻塞等待结束并释放资源,pthread_exit的参数会传给pthread_join.
- 设置线程为分离属性,线程中调用pthread_exit退出线程后系统自动释放资源。
- 在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程将终止,此时进程中有线程也将终止,因此在主线程中不能过早地从main函数返回;
- 如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才会结束;
3、pthread_join
函数pthread_join用来等待一个线程的结束,线程间同步的操作。
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable(合并)的。
(1)函数原型
int pthread_join( pthread_t thread, void * * value_ptr );
(2)参数
thread:线程ID
retval:用户定义的指针,用来存储被等待线程的返回值。
(3)返回值
0代表成功。 失败,返回的则是错误号。
(4)解释
- 代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
- 通过pthread_join()函数来使主线程阻塞等待其他线程退出,这样主线程可以清理其他线程的环境。但是还有一些线程,更喜欢自己来清理退出的状态,他们也不愿意主线程调用pthread_join来等待他们。我们将这一类线程的属性称为detached(分离的)。如果我们在调用 pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线程采用默认的属性,也就是jionable(此时不是detached)。
(1)作用
- 异步终结:当其他线程调用pthread_cancel的时候,线程就立刻被结束。
- 同步终结:同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)。当一个线程被按照默认的创建方式创建,那么它的属性是同步终结。
若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。
(2)函数原型
#include<pthread.h>
int pthread_cancel(pthread_t thread)
(3)返回值
成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
(4)用法
int pthread_setcancelstate(int state, int *oldstate)
- PTHREAD_CANCEL_ENABLE(缺省):收到信号后设为CANCLED状态
- PTHREAD_CANCEL_DISABLE:忽略CANCEL信号继续运行;
old_state如果不为 NULL则存入原来的Cancel状态以便恢复。
int pthread_setcanceltype(int type, int *oldtype)
- PTHREAD_CANCEL_DEFERRED:表示收到信号后继续运行至下一个取消点再退出
- PTHREAD_CANCEL_ASYNCHRONOUS:立即执行取消动作(退出)
oldtype如果不为NULL则存入原来的取消动作类型值。
void pthread_testcancel(void)
(5)程序实例
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void* func(void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //允许退出线程 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); //设置立即取消 while(1); return NULL; } int main(int argc,char *argv[]) { pthread_t thrd; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); if(pthread_create(&thrd,&attr,func,NULL)) { perror( "pthread_create error "); exit(EXIT_FAILURE); } if(!pthread_cancel(thrd)) { printf("pthread_cancel OK "); } sleep(10); return 0;
}
5、exit()
6、return
- pthread_join一般是主线程来调用,用来等待子线程退出,因为是等待,所以是阻塞的,一般主线程会依次join所有它创建的子线程。
- pthread_exit一般是子线程调用,用来结束当前线程。子线程可以通过pthread_exit传递一个返回值,而主线程通过pthread_join获得该返回值,从而判断该子线程的退出是正常还是异常。
二、资源清理
一旦又处于挂起状态的取消请求(即加锁之后,解锁之前),线程在执行到取消点时如果只是草草收场,这会将共享变量以及pthreads对象(例如互斥量)置于一种不一致状态,可能导致进程中其他线程产生错误结果、死锁,甚至造成程序崩溃。为避免这一问题:
使用清理函数pthread_cleanup_push()和pthread_cleanup_pop()来处理,这两个函数必需成对出现,不然会编译错误。
不论是可预见的线程种植还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证种植时能顺利的释放掉自己所占用的资源,包括单独申请的对内存,特别是锁资源,就是一个必需考虑的问题。
最近常出现的情形时资源独占锁的使用:
线程为了访问临界共享资源而为其加上锁,但在访问过程呗外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_clean_push()/pthread_cleanup_pop()函数对,用于自动释放资源----从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。
API定义如下:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,
void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
有三种情况线程清理函数会被调用:
- 线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
- 线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
- 线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.
注意:如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,是不会执行清理函数的。
void routine()函数可参照如下定义:
void *cleanup(void* p)
{
free(p);
printf("清理函数
");
}
线程主动清理过程的严谨写法:
void thread_fun(void*p)
{ p=malloc(20);
pthread_cleanup_push(cleanup,p);
printf("子线程
");
sleep(1); //系统调用,用来响应pthread_cancel函数
printf("是否杀死了线程
"); //如果线程在上一句被杀死,这一句不会被打印
pthread_exit(NULL); //不管线程是否被杀死,这一句都会检测清理函数,并执行
pthread_clean_pop(1);
}
注意:在子线程中如果申请了单独的堆空间,不应用free直接清理;因为假如在线程中直接free,如果,在free之后线程被取消,清理函数被执行,则会出现重复free的情况。
情况如下:
void thread_fun(void*p)
{
p=malloc(20);
pthread_cleanup_push(cleanup,p);
printf("子线程
");
sleep(1); //系统调用,用来响应pthread_cancel函数
printf("是否杀死了线程
"); //如果线程在上一句被杀死,这一句不会被打印
free(p); //不管线程是否被杀死,这一句都会检测清理函数,并执行<br>
//加入函数在此处被cancel ,可能会出现重复free
sleep(1);//在此处系统调用,响应pthread_cancel(),会执行清理函数.
return NULL;
pthread_clean_pop(1);//由于上一句return,所以这一句不执行,即清理函数不会执行
}
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg)
{
struct _pthread_cleanup_buffer _buffer;
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute)
_pthread_cleanup_pop (&_buffer, (execute));
}
可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
以下是使用方法:
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
.......
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);
必须要注意的是,如果线程处于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);
}
补充:
在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。