pthread_cleanup_push()

来源:互联网 发布:mysql 分页 nexttoken 编辑:程序博客网 时间:2024/05/22 10:54

线程终止时的清理

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行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_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);/* do some work */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); }

 

线程终止的同步及其返回值

一般情况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。

 

void pthread_exit(void *retval) int pthread_join(pthread_t th, void **thread_return)int pthread_detach(pthread_t th)

pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,如果thread_return不为NULL,则*thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态。

如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误。

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

 

关于pthread_exit()和return

理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上二者由于编译器的处理有很大的不同。

在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(可以说是进程的主线程)退出;而如果是return,编译器将使其调用进程退出的代码(如_exit()),从而导致进程及其所有线程结束运行。

其次,在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 如果你很害怕去面对一件事怎么办 孩子上幼儿园哭老师不理孩子怎么办 发现幼儿园给孩子吃药片该怎么办 做老师的打学生被家长投诉怎么办 学生认为老师向家长打报告怎么办 老师发打12分的试卷给家长怎么办 孩子在学校顶撞老师不让上学怎么办 被老师骂了不敢去学校怎么办 孩子不爱去幼儿园 总是哭怎么办呢 孩子在幼儿园被老师罚家长该怎么办 孩子不喜欢幼儿园里的体能课怎么办 初中叛逆期的孩子怎么办老师做法 二年级孩子不受老师待见怎么办 孩子该上初中了没学籍怎么办 四个多月的婴儿不喜欢看人怎么办 2个月婴儿不喜欢吃奶粉怎么办 想学习但是又学不进去怎么办 在省外读书 回来读高中学籍怎么办 一岁宝宝这几天不爱吃饭怎么办 2岁的宝宝吃多了怎么办 小孩吃了退烧药吐了怎么办 牙齿与牙齿之间有洞喜欢塞牙怎么办 胃有点烧心天天没食欲不饿怎么办 1岁半宝宝不吃饭光喝奶粉怎么办 一岁半宝宝光喝奶粉不吃饭怎么办 一岁半的宝宝光喝奶粉不吃饭怎么办 两岁半宝宝光喝奶粉不吃饭怎么办 宝宝9个月不好好吃饭奶粉怎么办 2岁半宝宝不愿意自己吃饭怎么办 一周岁多的宝宝不吃饭怎么办 一岁宝宝吃母乳不爱吃饭怎么办 三个月大的宝宝不肯喝奶粉怎么办 自己要上班婆婆带孩子看不惯怎么办 让农村婆婆来给我带孩子怎么办 吃的不好胃难受怎么办吃什么药 特别讨厌婆婆还需要她带小孩怎么办 一岁八个月宝宝不喜欢穿袜子怎么办 未来婆婆给我买衣服我不喜欢怎么办 孩子调皮被同学排斥不想上学怎么办 宝宝出生没人带怎么办自己要上班 孩子上幼儿园一年了还哭怎么办