【发布时间】:2023-01-24 03:24:41
【问题描述】:
我有一个程序,它使用thread_local std::shared_ptr 来管理一些主要在线程本地访问的对象。但是,当线程被加入并且线程本地shared_ptr正在析构时,如果程序是由MinGW(Windows 10)编译的,则在调试时总是有SIGSEGV。这是重现错误的最少代码:
// main.cpp
#include <memory>
#include <thread>
void f() {
thread_local std::shared_ptr<int> ptr = std::make_shared<int>(0);
}
int main() {
std::thread th(f);
th.join();
return 0;
}
如何编译:
g++ main.cpp -o build\main.exe -std=c++17
编译器版本:
>g++ --version
g++ (x86_64-posix-seh-rev2, Built by MinGW-W64 project) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
使用 gdb 运行时,当主线程等待 join() 时,它将在新线程中提供 SIGSEGV。它在 gcc、clang (Linux) 和 MSVC (Windows) 编译时工作正常。
我尝试调试发现,在调用RtlpWow64SetContextOnAmd64时,包含线程本地shared_ptr的一段连续内存被擦除为重复0xfeeefeee,然后销毁。框架:
RtlpWow64SetContextOnAmd64 0x00007ffd8f4deb5f
RtlpWow64SetContextOnAmd64 0x00007ffd8f4de978
SbSelectProcedure 0x00007ffd8f4ae2e0
CloseHandle 0x00007ffd8ce3655b
pthread_create_wrapper 0x00007ffd73934bac
_beginthreadex 0x00007ffd8e9baf5a
_endthreadex 0x00007ffd8e9bb02c
BaseThreadInitThunk 0x00007ffd8ec87614
RtlUserThreadStart 0x00007ffd8f4c26a1
大会:
...
mov %rax,(%rdi)
movdqu %xmm0,(%rsi) ; <------ erased here
call 0x7ffd8f491920 ; <ntdll!RtlReleaseSRWLockShared>
mov $0x1,%r9d
mov 0x30(%rsp),%rbx
...
后来 shared_ptr 被破坏,当读取 0xfeeefeee 时有 SIGSEGV。
我想知道:
- 为什么 MinGW(或 Windows 库?)在销毁前擦除线程本地存储?在我看来,擦除记忆应该只发生在破坏之后。我注意到如果
join()被替换为detach(),程序正常退出。也许join()做了一些事情来指示新线程擦除存储? - 这样的行为是否违反标准?我认为标准应该禁止在销毁前擦除内存。如果我弄错了,请纠正我。
【问题讨论】:
-
将
thread_local与局部变量混合使用是不常见的用例。我猜ptr在f结束时被销毁了。我建议将变量移动到全局范围。另一方面,它隐含了一个静态局部变量:stackoverflow.com/a/22794640/6752050 -
更喜欢使用 msys2 msys2.org
-
在 godbolt.org 上尝试使用不同的编译器版本,也许如果你选择了一个较新的版本,那么你正在使用的版本就不见了。
-
顺便说一句,我可以在 Win 11, g++ (Rev6, Built by MSYS2 project) 12.2.0 中重现这个问题。显示为
Thread 5 received signal SIGSEGV, Segmentation fault. [Switching to Thread 15196.0x27dc] 0x00007ff7e54133f4 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() () (gdb) x/i $rbp 0xfb957ff4b0: loopne 0xfb957ff4a6 -
@SolomonSlow 仅供参考“...如果 thread_local 是应用于块作用域变量的唯一存储类说明符,则还隐含了 static...”en.cppreference.com/w/cpp/language/storage_duration
标签: c++ multithreading c++11 mingw mingw-w64