为啥在 pthread_detach() 之后调用 pthread_exit() 在极少数情况下会导致 SEGV?

Posted

技术标签:

【中文标题】为啥在 pthread_detach() 之后调用 pthread_exit() 在极少数情况下会导致 SEGV?【英文标题】:Why does pthread_exit() in rare cases cause a SEGV when called after pthread_detach()?为什么在 pthread_detach() 之后调用 pthread_exit() 在极少数情况下会导致 SEGV? 【发布时间】:2012-07-11 15:47:53 【问题描述】:

在我对pthread_join() 的调用中,当我的应用程序正在关闭时,我在 C++ 中获得了一个无法轻易重现的 SEGV(它发生在大约 100,000 次测试运行中)。我检查了 errno 的值,它是零。这是在 Centos v4 上运行的。

pthread_join() 在什么条件下会获得 SEGV?这可能是某种竞争条件,因为它极为罕见。有人建议我不应该调用 pthread_detach() 和 pthread_exit(),但我不清楚为什么。

我的第一个工作假设是 pthread_join() 正在被调用,而 pthread_exit() 仍在另一个线程中运行,这会导致 SEGV,但许多人表示这不是问题。

在应用程序退出期间在主线程中获取 SEGV 的失败代码大致如下(为简洁起见省略了错误返回代码检查):

// During application startup, this function is called to create the child thread:

return_val = pthread_create(&_threadId, &attr,
                            (void *(*)(void *))initialize,
                            (void *)this);

// Apparently this next line is the issue:
return_val = pthread_detach(_threadId);

// Later during exit the following code is executed in the main thread:

// This main thread waits for the child thread exit request to finish:

// Release condition so child thread will exit:
releaseCond(mtx(), startCond(), &startCount);

// Wait until the child thread is done exiting so we don't delete memory it is
// using while it is shutting down.
waitOnCond(mtx(), endCond(), &endCount, 0);
// The above wait completes at the point that the child thread is about
// to call pthread_exit().

// It is unspecified whether a thread that has exited but remains unjoined
// counts against PTHREAD_THREADS_MAX, hence we must do pthread_join() to
// avoid possibly leaking the threads we destroy.
pthread_join(_threadId, NULL); // SEGV in here!!!

退出时加入的子线程运行以下代码,该代码从上面在主线程中调用releaseCond()的点开始:

// Wait for main thread to tell us to exit:
waitOnCond(mtx(), startCond(), &startCount);

// Tell the main thread we are done so it will do pthread_join():
releaseCond(mtx(), endCond(), &endCount);
// At this point the main thread could call pthread_join() while we 
// call pthread_exit().

pthread_exit(NULL);

线程似乎正常启动,在应用程序启动期间创建过程中没有产生错误代码,线程正确执行了它的任务,大约在应用程序退出前大约五秒钟。

什么可能导致这种罕见的 SEGV 发生,以及我应该如何针对它进行防御性编程。一种说法是我对 pthread_detach() 的调用是问题所在,如果是这样,我的代码应该如何更正。

【问题讨论】:

您是否检查了本地文档以了解您的 pthread_join() 并非所有都符合标准,您可能必须传递一个非 NULL 指针。 “子线程”如何知道何时调用该退出代码?该退出代码是如何被调用的? Centos 4?很复古。 LinuxThreads 还是 NPTL? @Loki man pthread_join 允许空指针。 @user315052 子线程知道因为主线程将状态变量设置为退出,然后释放启动条件,所以子线程将检查状态变量以查看请求的操作。为简洁起见,我省略了这一点。退出代码由从主线程管理子线程的对象的析构函数调用。 【参考方案1】:

假设:

    pthread_create 返回零(您正在检查它,对吗?) attr 是一个有效的 pthread_attr_t 对象(你是如何创建它的?为什么不直接传递 NULL 呢?) attr 没有指定线程要分离创建 您没有在其他地方的线程上调用pthread_detachpthread_join

...那么pthread_join 失败是“不可能的”,你要么有其他内存损坏,要么运行时出现错误。

[更新]

pthread_detach 的基本原理部分说:

*pthread_join*() 或 *pthread_detach*() 函数最终应该是 为创建的每个线程调用,以便存储关联 线程可能会被回收。

虽然没有说这些是互斥的,但pthread_join documentation 指定:

如果线程指定的值,则行为未定义 *pthread_join*() 的参数不引用可连接线程。

我很难找到说明分离线程不可连接的确切措辞,但我很确定这是真的。

因此,请致电 pthread_joinpthread_detach,但不能同时致电。

【讨论】:

1) 检查返回值是否为零。 2) 有效,使用pthread_attr_init(&attr);pthread_attr_setstacksize(&attr, stackSize); 到64 MB 堆栈大小。 3) Attr 中没有其他内容,所有其他内容均为默认值。 4) 在pthread_create() 之后不久,我确实 致电pthread_detach(_threadId);。那么我应该如何更新来纠正这个问题呢? 你不需要加入一个分离的线程;这样做是错误的。回家后我会用参考更新我的答案... 我也很惊讶在 POSIX 文档(或至少一个更容易找到的文档)中没有更明确地描述什么是“可连接线程”。另一个可能相关的信息是在pthread_attr_getdetachstate() 的文档中:pubs.opengroup.org/onlinepubs/9699919799/functions/…,它说“如果线程是分离创建的,那么 pthread_detach() 或 pthread_join() 函数使用新创建的线程的 ID是一个错误”【参考方案2】:

如果您阅读了pthread_join 和pthread_exit 以及相关页面的标准文档,则连接会暂停执行“直到目标线程终止”,并且调用 pthread_exit 的线程在调用 pthread_exit 之前不会终止,那怎么办你担心的不是问题。

您可能在某处损坏了内存(如 Nemo 建议的那样),或从清理处理程序调用 pthread_exit(如 user315052 建议的那样),或其他原因。但这不是“pthread_join() 和 pthread_exit() 之间的竞争条件”,除非您使用的是错误或不兼容的实现。

【讨论】:

【参考方案3】:

没有足够的信息来全面诊断您的问题。我同意其他发布的答案,即问题更可能是代码中未定义的行为,而不是pthread_joinpthread_exit 之间的竞争条件。但我也同意这种竞争的存在会构成 pthread 库实现中的错误。

关于pthread_join

return_val = pthread_create(&_threadId, &attr,
                            (void *(*)(void *))initialize,
                            (void *)this);
//...
pthread_join(_threadId, NULL); // SEGV in here!!!

看起来加入是在一个类中。这开启了在main 尝试进行连接时删除对象的可能性。如果pthread_join 正在访问已释放的内存,则结果是未定义的行为。我倾向于这种可能性,因为访问释放的内存经常未被检测到。

关于pthread_exit:Linux 上的手册页和 POSIX 规范状态:

当除第一次调用 main() 的线程之外的线程从用于创建它的启动例程返回时,会进行对 pthread_exit() 的隐式调用。函数的返回值作为线程的退出状态。

如果从作为隐式或显式调用 pthread_exit() 的结果而调用的取消清理处理程序或析构函数调用 pthread_exit() 的行为是未定义的。

如果pthread_exit 调用是在清理处理程序中进行的,您将有未定义的行为。

【讨论】:

是的,这是在一个类中,ctor 执行 pthread_create(),dtor 执行 pthread_join()。

以上是关于为啥在 pthread_detach() 之后调用 pthread_exit() 在极少数情况下会导致 SEGV?的主要内容,如果未能解决你的问题,请参考以下文章

如果在已经返回的线程上调用pthread_detach会发生什么

linux下多线程之pthread_detach(pthread_self())

pthread_detach 上的错误文件描述符

pthread_join和pthread_detach的用法

pthread_detach(pthread_self())

为啥在 setState 之后调用 getDerivedStateFromProps?