12

帮助客户解决他们遇到的问题。我更像是一个系统管理员/DBA 人,所以我正在努力帮助他们。他们说这是内核/环境中的错误,在我坚持认为它在他们的代码中或寻求操作系统的供应商支持之前,我试图证明或反驳这一点。

发生在 Red Hat 和 Oracle Enterprise Linux 5.7(和 5.8)上,应用程序是用 C++ 编写的

他们遇到的问题是主线程启动了一个单独的线程来执行可能长时间运行的 TCP connect() [客户端连接到服务器]。如果“长时间运行”方面花费的时间太长,他们会取消线程并启动另一个线程。

这样做是因为我们不知道服务器程序的状态:

  • 服务器程序启动并运行 --> 立即接受连接
  • 服务器程序未运行,机器和网络正常 --> 连接立即失败,出现错误“连接被拒绝”
  • 机器或网络崩溃或关闭 --> 连接需要很长时间才能失败,并出现错误“没有到主机的路由”

问题是取消已锁定互斥锁的线程(设置清除处理程序以解锁互斥锁)有时不会解锁互斥锁。

这使得主线程挂起试图锁定互斥锁。

详细环境信息:

  • glibc-2.5-65
  • glibc-2.5-65
  • libcap-1.10-26
  • 内核调试-2.6.18-274.el5
  • glibc-headers-2.5-65
  • glibc-common-2.5-65
  • libcap-1.10-26
  • 内核-doc-2.6.18-274.el5
  • 内核-2.6.18-274.el5
  • kernel-headers-2.6.18-274.el5
  • glibc-devel-2.5-65

代码是用:c++ -g3 tst2.C -lpthread -o tst2 构建的

非常感谢任何建议和指导

4

2 回答 2

18

取消的线程不会解锁它们持有的互斥锁是正确的,您需要手动安排它,这可能很棘手,因为您需要非常小心地在每个可能的取消点周围使用正确的清理处理程序。假设您正在使用pthread_cancel取消线程并设置清理处理程序pthread_cleanup_push来解锁互斥锁,您可以尝试几种替代方法,它们可能更容易正确处理,因此可能更可靠。

使用RAII解锁互斥锁更可靠。在 GNU/Linuxpthread_cancel上实现了一个特殊的异常类型__cxxabi::__forced_unwind,所以当一个线程被取消时,一个异常被抛出并且堆栈被展开。__forced_unwind如果互斥锁被 RAII 类型锁定,那么如果堆栈被异常 解开,它的析构函数将保证运行。Boost Thread提供了一个可移植的 C++ 库,它封装了 Pthreads,并且更易于使用。它提供了 RAII 类型boost::mutex和其他有用的抽象。Boost Thread 还提供了自己的“线程中断”机制,与 Pthread 取消类似但又不一样,Pthread 取消点(如connect) 不是 Boost Thread 中断点,这对某些应用程序很有帮助。但是,在您的客户的情况下,因为取消点是中断connect呼叫,他们可能确实希望坚持使用 Pthread 取消。GNU/Linux 将取消作为例外实现的(不可移植的)方式意味着它可以很好地与boost::mutex.

当您使用 C++ 编写代码时,确实没有任何理由显式锁定和解锁互斥锁,恕我直言,C++最重要和最有用的特性是析构函数,它非常适合自动释放资源,例如互斥锁。

另一种选择是使用健壮的互斥锁,它是通过在初始化互斥锁之前调用pthread_mutexattr_setrobusta创建的。pthread_mutexattr_t如果一个线程在持有一个健壮的互斥锁时死掉了,内核会记下它,以便下一个试图锁定互斥锁的线程得到特殊的错误代码EOWNERDEAD。如果可能,新线程可以使该线程保护的数据再次保持一致,并取得互斥锁的所有权。这比简单地使用 RAII 类型来锁定和解锁互斥锁要难于正确使用。

一种完全不同的方法是确定在调用时是否真的需要持有互斥锁connect。在缓慢的操作期间持有互斥锁不是一个好主意。connect如果成功锁定互斥锁并更新受互斥锁保护的任何共享数据,您就不能打电话吗?

我的偏好是同时使用 Boost Thread 并避免长时间持有互斥锁。

于 2013-01-10T22:45:32.107 回答
4

他们遇到的问题是主线程启动了一个单独的线程来执行可能长时间运行的 TCP connect() [客户端连接到服务器]。如果“长时间运行”方面花费的时间太长,他们会取消线程并启动另一个线程。

微不足道的修复——不要取消线程。它有什么害处吗?如有必要,让线程检查(当connectfinally 完成时)是否仍然需要连接,如果不需要,则关闭它,释放互斥体并终止。您可以使用受互斥锁保护的布尔变量来执行此操作。

此外,线程在等待网络 I/O 时不应持有互斥锁。互斥锁应该只用于快速且主要受 CPU 限制或可能受本地磁盘限制的事物。

最后,如果您觉得需要从外部伸手并迫使线程做某事,请退后一步。为该线程编写了代码。如果您觉得需要,这意味着您没有编写该线程来执行您真正希望它执行的操作。解决方法是修改线程以执行您真正想要的,并且只执行您真正想要的。这样你就不必从外面“推它”了。

于 2013-01-11T01:24:54.207 回答