POSIX的线程的取消点(Cancellation Point)的概念和实现方式
来源:互联网 发布:nginx允许ip和域名 编辑:程序博客网 时间:2024/06/06 20:00
摘要:
这篇文章主要从一个 Linux 下一个 pthread_cancel 函数引起的多线程死锁小例子出发来说明 Linux 系统对 POSIX 线程取消点的实现方式,以及如何避免因此产生的线程死锁。
目录:
1. 一个 pthread_cancel 引起的线程死锁小例子
2. 取消点(Cancellation Point)
3. 取消类型(Cancellation Type)
4. Linux 的取消点实现
5. 对示例函数进入死锁的解释
6. 如何避免因此产生的死锁
7. 结论
8. 参考文献
1. 一个 pthread_cancel 引起的线程死锁小例子
#include pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* thread0(void* arg){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);pthread_mutex_unlock(&mutex);pthread_exit(NULL);}void* thread1(void* arg){sleep(10);pthread_mutex_lock(&mutex);pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mutex);pthread_exit(NULL);}int main(){pthread_t tid[2];if (pthread_create(&tid[0], NULL, &thread0, NULL) != 0) {exit(1);}if (pthread_create(&tid[1], NULL, &thread1, NULL) != 0) {exit(1);}sleep(5);pthread_cancel(tid[0]);pthread_join(tid[0], NULL);pthread_join(tid[1], NULL);pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;}
2. 取消点(Cancellation Point)
pthread_join(3)pthread_cond_wait(3)pthread_cond_timedwait(3)pthread_testcancel(3)sem_wait(3)sigwait(3)
但是,令人迷惑不解的是,所有介绍 Cancellation Points 的文章都仅仅说,当线程被取消后,将继续运行到取消点并发生取消动作。但我们注意到上面例子中 pthread_cancel 前面 main 函数已经 sleep 了 5 秒,那么在 pthread_cancel 被调用时,thread0 到底运行到 pthread_cond_wait 没有?
如果 thread0 运行到了 pthread_cond_wait,那么照上面的说法,它应该继续运行到下一个取消点并发生取消动作,而后面并没有取消点,所以 thread0 应该运行到 pthread_exit 并结束,这时 mutex 就会被解锁,这样就不应该发生死锁啊。
3. 取消类型(Cancellation Type)
我们会发现,通常的说法:某某函数是 Cancellation Points,这种方法是容易令人混淆的。因为函数的执行是一个时间过程,而不是一个时间点。其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。
POSIX 的取消类型有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。
4. Linux 的取消点实现
下面我们看 Linux 是如何实现取消点的。(其实这个准确点儿应该说是 GNU 取消点实现,因为 pthread 库是实现在 glibc 中的。) 我们现在在 Linux 下使用的 pthread 库其实被替换成了 NPTL,被包含在 glibc 库中。
以 pthread_cond_wait 为例,glibc-2.6/nptl/pthread_cond_wait.c 中:
/* Enable asynchronous cancellation. Required by the standard. */cbuffer.oldtype = __pthread_enable_asynccancel ();/* Wait until woken by signal or broadcast. */lll_futex_wait (&cond->__data.__futex, futex_val);/* Disable asynchronous cancellation. */__pthread_disable_asynccancel (cbuffer.oldtype);
我们可以看到,在线程进入等待之前,pthread_cond_wait 先将线程取消类型设置为异步取消(__pthread_enable_asynccancel),当线程被唤醒时,线程取消类型被修改回延迟取消 __pthread_disable_asynccancel 。
这就意味着,所有在 __pthread_enable_asynccancel 之前接收到的取消请求都会等待 __pthread_enable_asynccancel 执行之后进行处理,所有在 __pthread_disable_asynccancel 之前接收到的请求都会在 __pthread_disable_asynccancel 之前被处理,所以真正的 Cancellation Point 是在这两点之间的一段时间。
5. 对示例函数进入死锁的解释
当 main 函数中调用 pthread_cancel 前,thread0 已经进入了 pthread_cond_wait 函数并将自己列入等待条件的线程列表中(lll_futex_wait)。这个可以通过 GDB 在各个函数上设置断点来验证。
当 pthread_cancel 被调用时,tid[0] 线程仍在等待,取消请求发生在 __pthread_disable_asynccancel 前,所以会被立即响应。但是 pthread_cond_wait 为注册了一个线程清理程序(glibc-2.6/nptl/pthread_cond_wait.c):
/* Before we block we enable cancellation. Therefore we have toinstall a cancellation handler. */__pthread_cleanup_push (&buffer, __condvar_cleanup, &cbuffer);
那么这个线程清理程序 __condvar_cleanup 干了什么事情呢?我们可以注意到在它的实现最后(glibc-2.6/nptl/pthread_cond_wait.c):/* Get the mutex before returning unless asynchronous cancellationis in effect. */__pthread_mutex_cond_lock (cbuffer->mutex);}
哦,__condvar_cleanup 在最后将 mutex 重新锁上了。而这时候 thread1 还在休眠(sleep(10)),等它醒来时,mutex 将会永远被锁住,这就是为什么 thread1 陷入无休止的阻塞中。6. 如何避免因此产生的死锁
void cleanup(void *arg){pthread_mutex_unlock(&mutex);}void* thread0(void* arg){pthread_cleanup_push(cleanup, NULL); // thread cleanup handlerpthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);pthread_mutex_unlock(&mutex);pthread_cleanup_pop(0);pthread_exit(NULL);}
这样,当线程被取消时,先执行 pthread_cond_wait 中注册的线程清理函数 __condvar_cleanup,将 mutex 锁上,再执行 thread0 中注册的线程处理函数 cleanup,将 mutex 解锁。这样就避免了死锁的发生。7. 结论
8. 参考文献
[1] W. Richard Stevens, Stephen A. Rago: Advanced Programming in the UNIX Environment, 2nd Edition.
[2] Linux Manpage
- POSIX的线程的取消点(Cancellation Point)的概念和实现方式
- POSIX的线程的取消点(Cancellation Point)的概念和实现方式
- POSIX 线程取消点的 Linux 实现
- POSIX 线程取消点的 Linux 实现
- POSIX 线程取消点的 Linux 实现
- POSIX 线程取消点的 Linux 实现
- POSIX 线程取消点的 Linux 实现(zz)
- POSIX线程的创建和取消
- posix线程-线程的取消
- 线程取消点的困惑
- 线程取消点的困惑
- POSIX的概念
- Solaris 线程和 POSIX 线程的 API
- AOP中通知(advice)、切点(pointcut)和连接点(join point)的概念
- POSIX线程的同步
- 线程的取消和中断
- 线程的取消和关闭
- 【Posix线程】pthread_clean_push和pthread_clean_up的使用
- 孙陶然致电商创业者:不存在不赚钱的商业模式
- java中静态代码块的用法 static用法详解
- 关于LINUX fread的问题
- [解决方法]org.dbunit.dataset.NoSuchTableException: Did not find table 'tab1' in schema 'null'
- MySQL的双机热备份(一)--MySQL的主从复制
- POSIX的线程的取消点(Cancellation Point)的概念和实现方式
- 吸取教训:做事情一定要要形成文档
- SSH C3P0连接池连接数总结
- 开始注意技术积累
- Linux目录结构
- 要执行请求的操作,WordPress 需要访问您网页服务器的权限。 请输入您的 FTP 登录XXXX完美解决方法
- mysql 日志清理
- oracle导入导出
- hibernate连接池配置