RnM*_*Mss 22 c++ multithreading memory-leaks visual-studio
以下代码
#include <iostream>
#include <future>
#include <thread>
#include <mutex>
std::mutex m;
struct Foo {
Foo() {
std::unique_lock<std::mutex> lock{m};
std::cout <<"Foo Created in thread " <<std::this_thread::get_id() <<"\n";
}
~Foo() {
std::unique_lock<std::mutex> lock{m};
std::cout <<"Foo Deleted in thread " <<std::this_thread::get_id() <<"\n";
}
void proveMyExistance() {
std::unique_lock<std::mutex> lock{m};
std::cout <<"Foo this = " << this <<"\n";
}
};
int threadFunc() {
static thread_local Foo some_thread_var;
// Prove the variable initialized
some_thread_var.proveMyExistance();
// The thread runs for some time
std::this_thread::sleep_for(std::chrono::milliseconds{100});
return 1;
}
int main() {
auto a1 = std::async(std::launch::async, threadFunc);
auto a2 = std::async(std::launch::async, threadFunc);
auto a3 = std::async(std::launch::async, threadFunc);
a1.wait();
a2.wait();
a3.wait();
std::this_thread::sleep_for(std::chrono::milliseconds{1000});
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在macOS中编译并运行宽度clang:
clang++ test.cpp -std=c++14 -pthread
./a.out
Run Code Online (Sandbox Code Playgroud)
得到了结果
Run Code Online (Sandbox Code Playgroud)Foo Created in thread 0x70000d9f2000 Foo Created in thread 0x70000daf8000 Foo Created in thread 0x70000da75000 Foo this = 0x7fd871d00000 Foo this = 0x7fd871c02af0 Foo this = 0x7fd871e00000 Foo Deleted in thread 0x70000daf8000 Foo Deleted in thread 0x70000da75000 Foo Deleted in thread 0x70000d9f2000
在Visual Studio 2015 Update 3中编译并运行:
Run Code Online (Sandbox Code Playgroud)Foo Created in thread 7180 Foo this = 00000223B3344120 Foo Created in thread 8712 Foo this = 00000223B3346750 Foo Created in thread 11220 Foo this = 00000223B3347E60
析构函数不会被调用.
这是一个错误还是一些未定义的灰色区域?
PS
如果最后的睡眠std::this_thread::sleep_for(std::chrono::milliseconds{1000});时间不够长,有时您可能看不到所有3个"删除"消息.
当使用std::thread而不是std::async在两个平台上调用析构函数时,将始终打印所有3个"删除"消息.
Pau*_*ers 17
介绍性说明:我现在已经对此有了更多了解,因此重新编写了我的答案.感谢@super,@ MM和(后来)@DavidHaim和@NoSenseEtAl让我走上正轨.
文艺青年最爱的微软的实施std::async是不符合的,但他们有他们的理由和他们所做的实际上是有用的,一旦你正确地理解它.
对于那些不想要它的人来说,编写一个替代替代品并不是很困难,因为替换替代品std::async在所有平台上都以相同的方式工作.我在这里发了一个.
编辑:哇,这些天MS 有多开放,我喜欢它,请参阅:https://github.com/MicrosoftDocs/cpp-docs/issues/308
让我们开始吧. cppreference有这个说法(强调和删除我的):
模板函数异步
async运行该函数f(可能可选地在一个单独的线程中,该线程可能是线程池的一部分).
但是,C++标准说:
如果
launch::async设置为policy,[std::async]调用[函数f] 就好像在新的执行线程中 ...
哪个是正确的?OP发现,这两个语句具有非常不同的语义.当然,标准是正确的,因为clang和gcc都显示,为什么Windows实现有所不同?就像很多事情一样,它归结为历史.
......微软
std::async以PPL(并行模式库)的形式实现[ ] ... [和]我可以理解这些公司急于改变规则并使这些库可以访问std::async,特别是如果它们可以显着改善表现............微软希望改变
std::async调用时的语义,launch_policy::async.我认为在随后的讨论中几乎已经排除了......(理由如下,如果你想了解更多,那么阅读链接,这是值得的).
PPL基于Windows对ThreadPools的内置支持,所以@super是对的.
那么Windows线程池做了什么以及它有什么用呢?好吧,它旨在以有效的方式管理经常运行的,短期运行的任务,所以第1点不要滥用它,但我的简单测试表明,如果这是你的用例,那么它可以提供显着的效率.它本质上是两件事
std::async将阻塞,直到线程变为空闲.在我的机器上,这个数字是768.所以知道这一切,我们现在可以解释OP的观察结果:
为启动的三个任务中的每个任务创建一个新线程main()(因为它们都不会立即终止).
这三个线程中的每一个都创建一个新的线程局部变量Foo some_thread_var.
这三个任务都运行完成,但它们运行的线程仍然存在(休眠).
程序然后休眠一会儿然后退出,留下3个线程局部变量未被破坏.
我运行了一些测试,除此之外我发现了一些关键的东西:
std::async因此,呼叫可能需要一段时间才能返回(在我的测试中最多300毫秒).与此同时,它只是徘徊,希望它的船将进来.这种行为有记录,但我在这里称呼它,以防它让你感到惊讶.结论:
Microsoft的实现std::async是不符合要求的,但它显然是为特定目的而设计的,其目的是充分利用Win32 ThreadPool API.你可以肆无忌惮地蔑视标准,但是这种方式已经很长时间了,他们可能有(重要的)客户依赖它.我会请他们在他们的文件中说出来.如果不这样做即是犯罪.
它是不是安全使用thread_local变量std::async在Windows的任务.只是不要这样做,它会以泪水结束.