帮助客户解决他们遇到的问题。我更像是一个系统管理员/ DBA人,所以我正在努力帮助他们。他们说它是内核/环境中的一个错误,我试图证明或反驳它之前我坚持认为它是在他们的代码中或寻求供应商支持OS。
发生在Red Hat和Oracle Enterprise Linux 5.7(和5.8)上,应用程序是用C ++编写的
他们遇到的问题是主线程启动一个单独的线程来执行可能长时间运行的TCP connect()[连接到服务器的客户端]。 如果“长时间运行”方面花费的时间太长,它们会取消该线程并启动另一个线程。
这样做是因为我们不知道服务器程序的状态:
问题是取消锁定了互斥锁的线程 (设置清理处理程序以解锁互斥锁)有时不会解锁互斥锁。
这会使主线程挂起,试图锁定互斥锁。
详细环境信息:
代码是用以下内容构建的: c ++ -g3 tst2.C -lpthread -o tst2
非常感谢任何建议和指导
答案 0 :(得分:15)
取消的线程没有解锁它们所持有的互斥锁是正确的,你需要安排手动进行,这可能很棘手,因为你需要非常小心地在每个可能的取消点周围使用正确的清理处理程序。假设您正在使用pthread_cancel
取消线程并使用pthread_cleanup_push
设置清理处理程序来解锁互斥锁,那么您可以尝试一些替代方案,这可能更容易实现,因此可能更多可靠。
使用RAII解锁互斥锁将更可靠。在GNU / Linux上pthread_cancel
是使用类型__cxxabi::__forced_unwind
的特殊异常实现的,因此当一个线程被取消时,抛出异常并解除堆栈。如果互斥锁被RAII类型锁定,那么如果堆栈被__forced_unwind
异常展开,则它的析构函数将保证运行。 Boost Thread
提供了一个可移植的C ++库,它包装Pthreads并且更容易使用。它提供RAII类型boost::mutex
和其他有用的抽象。 Boost Thread还提供了自己的“线程中断”机制,类似于Pthread取消但不相同,并且Pthread取消点(例如connect
)不是Boost Thread中断点,这对某些应用程序很有帮助。但是在您的客户端的情况下,由于取消点是中断connect
调用,他们可能希望坚持使用Pthread取消。 (非可移植)方式GNU / Linux实现取消作为例外意味着它将与boost::mutex
一起使用。
当您使用C ++编写时,没有理由明确锁定和解锁互斥锁,恕我直言,C ++的最重要和最有用的功能是析构函数,非常适合自动释放资源,如互斥锁。
另一种选择是使用健壮的互斥锁,这是通过在初始化互斥锁之前在pthread_mutexattr_t
上调用pthread_mutexattr_setrobust
来创建的。如果一个线程在持有一个健壮的互斥锁时死掉,内核会记下它,以便试图锁定互斥锁的下一个线程获得特殊的错误代码EOWNERDEAD
。如果可能,新线程可以使线程保护的数据再次保持一致并获得互斥锁的所有权。这比使用RAII类型锁定和解锁互斥锁要困难得多。
完全不同的方法是在调用connect
时确定是否确实需要保持互斥锁。在慢速操作期间持有互斥锁并不是一个好主意。如果成功锁定互斥锁并更新互斥锁保护的共享数据,您是否可以调用connect
?
我倾向于使用Boost Thread并避免长时间持有互斥锁。
答案 1 :(得分:4)
他们遇到的问题是主线程启动一个单独的线程来执行可能长时间运行的TCP connect()[连接到服务器的客户端]。如果“长时间运行”方面花费的时间太长,它们会取消该线程并启动另一个线程。
琐碎的修复 - 不要取消线程。它有害吗?如有必要,请检查线程(当connect
最终完成时)是否仍然需要连接,如果没有,请关闭它,释放互斥锁,然后终止。您可以使用受互斥锁保护的布尔变量来执行此操作。
此外,线程在等待网络I / O时不应持有互斥锁。互斥锁应仅用于快速且主要受CPU限制或可能受本地磁盘限制的内容。
最后,如果您觉得需要从外部进入并迫使线程做某事,请退后一步。 您编写了该线程的代码。如果您觉得需要,那就意味着您没有编写该线程来执行您真正想要的操作。修复是修改线程做什么,只做你真正想要的。那么你就不必从外面“推它”了。