2011年(17)
分类: LINUX
2011-12-05 19:41:46
初看这个主题时,觉得实在简单。在我印象中,线程取消的实现通常是声明一个全局变量来代表取消标志,一个线程在最开始的大while中判断该标志是否被设置,如果被设置就跳出循环。但是这有一个问题是,如果程序中有n个线程都有可能被取消,那么我们是否要声明n个全局变量来单独控制它们的取消?POSIX提供了一些有效的API来简化我们对线程取消的操作(不过这背后还是有很多复杂的概念)。从很多书上把线程的取消单独作为一节或一章,足可窥见它的特殊性。
考虑一下为什么想要取消我们为一些原因创建的线程呢?比如在一个有GUI的界面,我们可能会使用一个后台线程处理一些费事的操作,比如,查找、准备打印文件,界面通常会为用户提供一个停止按钮来取消这些操作(在word中,可以取消要打印的文件)。这个时候就需要线程取消操作了。
线程的取消
该函数发送一个取消请求到指定的线程中。
int pthread_cancel(pthread_t thread)
注意点:
1.当该函数发送取消请求后,它立即返回,并不等待指定线程终止后才返回。
2.指定线程什么时候返回,完全依赖于线程取消的状态和类型。
3.当进程被取消后,pthread_join()函数返回PTHREAD_CANCELED。
线程取消状态
设置线程是否能被取消。
int pthread_setcancelstate(int state, int *oldstate)
注意点:
1.线程PTHREAD_CANCEL_DISABLE被设置后收到的取消请求都将被挂起(记住是被挂起,不是被丢失)。如果程序中要执行一段不能被打断的操作时,设置该标志。
2.PTHREAD_CANCEL_ENABLE,重新让线程恢复到可以接受取消请求的状态。该状态是线程的默认状态。
线程取消类型
int pthread_setcanceltype(int type, int *oldtype)
注意点:
1.线程的取消类型只有在PTHREAD_CANCEL_ENABLE标志下才有意义。
2.PTHREAD_CANCEL_ASYNCHRONOUS,异步标志。表示线程能在任意时刻被取消(该标志很少使用,最后讨论)。
3.PTHREAD_CANCEL_DEFERRED,推迟取消。取消请求要等到取消点(cancellation point)才能被处理。
取消点(Cancellation Point)
在推迟取消标志设置后,取消请求只有在某些函数中才能被处理,这些函数就叫做取消点。
图选自The linux programming interface-chapter32
从上图可推断,大部分的取消点都是会让线程阻塞的函数。当一个线程到达一个取消点时,系统决定是否有一个未解决的针对目标线程的取消请求。如果目标线程从一个取消点返回后,另外的线程针对目标线程调用了pthread_cancel(),就存在一个未解决的取消。如果这一取消是未解决的,系统将很快开始调用清除函数(后面会提到),然后线程终止。
有时我们的线程中根本就没有用到上面所列的取消点,那是否意味着线程就不能被取消?是的,不能被取消。所以,我们需要下面这个函数来解决这个问题。
线程取消测试
void pthread_testcancel(void)
在线程中添加这个API来检查是否有取消请求到来,以便处理这个请求。
再考虑下面这些场景:线程动态的分配了一些空间,或线程拥有某些锁。但它在收到取消请求后自动的退出了。那分配的空间将会造成内存泄露,没释放的锁将会让其他线程一直被阻塞,这些都是很严重的问题。POSIX线程为了解决该问题,制定了下面的API。
线程清除
void pthread_cleanup_push(void (*routine)(void *), void *arg)
void pthread_cleanup_pop(int execute)
注意点:
1.pthread_cleanup_push()把清除函数加到栈(该栈专门为清除函数准备)中,pthread_cleanup_pop()把栈顶中的清除函数删除。当线程被取消或它调用pthread_exit()退出时,系统从栈顶依次弹出清除函数,并执行。
2.当线程执行到底部时都未被取消,那么调用pthread_cleanup_pop()来清除该处理函数(参数为0)。
3.当pthread_cleanup_pop()以非零值被调用时,就算线程没被取消,清除处理函数也要被执行。
4.这两个函数可以被设计为宏,比如在pthread_cleanup_push中包含一个{,而在pthread_cleanup_pop中包含一个}。这样在代码中,必须要求这两个函数成对的出现,必须在同一个代码块中。
现在来讨论一下异步取消
推迟取消必要要在取消点才能取消线程,而异步取消却是直接终止线程,并不等待到某个取消点上。值得注意的是异步取消也会调用清除函数,但是线程在被取消时,我们根本不知道线程是在什么条件下被取消的(内存是否已经被分配,互斥量是否被锁住了等),也就无法在清除函数中执行相对应的逆操作。
当你在线程中调用malloc时,系统为你分配一些堆内存。Malloc可能在很多地方被异步取消打断(在分配内存前被打断,在分配内存后,在保持地址返回前被打断等)。你不能判断内存是否被分配了,也就是说在清除函数中你不知道该不该释放内存。在你调用pthread_mutex_lock时,你也无法知道到底锁住没有,该不该在清除函数中释放该锁。
那异步取消就一点用处都没有了么?当然有用,不然创造它干什么!对于一个运行紧密计算循环的线程时,不断地调用pthread_testcancel()是昂贵的,我们只能用异步取消来完成取消。
当需要使用异步取消时,有一些基本准则必须遵守:不能分配任何资源,不能要求任何互斥量,锁等。