请教一个 shared_ptr 内存泄漏的问题

2022-05-10 11:19:39 +08:00
 guang19

如题,我正在用 cpp 写一个线程池的 submit 函数,为了可以让用户可以异步的获取到函数执行的返回值,我使用了 future 和 promise 特性,一部分代码如下:

template <typename Fn, typename... Args, typename HasRet, typename Redundant>
::std::future<typename ::std::result_of<Fn(Args...)>::type>
ThreadPool::submit(Fn&& func, Args... args)
{
	if (!running_)
	{
		LOGS_FATAL << "thread pool has not been initialized";
	}
	using RetType = typename ::std::result_of<Fn(Args...)>::type;
	LockGuardType lock(mutex_);
	auto promise = ::std::make_shared<::std::promise<RetType>>();
	auto future = promise->get_future();
	::std::function<RetType()> tmpTask = ::std::bind(::std::forward<Fn>(func), 					::std::forward<Args>(args)...);
1 )	TaskType task = [t = ::std::move(tmpTask), promise]() mutable
	{
2 )		assert(promise.use_count() == 1);
		promise->set_value(t());
	};
3 )    assert(promise.use_count() == 2);
    ......
    return future;
}

1 )处我用的 lambda 是值捕获 promise,3 )处的 use_count 总为 2 这没问题,现在的问题不知为什么 2 )处的 use_count 却不稳定,有时为 3 ,有时为 1 ? 下面为测试代码:

TEST_F(ThreadPoolTest, SubmitHasRetval)
{
	::std::function<int32_t ()> f = []() -> int32_t
	{
		return 5;
	};
	auto future1 = threadPool->submit(f);
	future1.wait();
	ASSERT_EQ(future1.get(), 5);
	auto future2 = threadPool->submit(::std::move(f));
	future2.wait();
	ASSERT_EQ(future2.get(), 5);
}

debug 模式下正常结果为:

2022-05-10 11:09:44.942 [9924] DEBUG ThreadPool.cpp:111 - thread pool init success
2022-05-10 11:09:44.943 [9924] DEBUG ThreadPool.cpp:128 - thread pool has been shutdown

错误结果为:

... Assertion `promise.use_count() == 1' failed.
2022-05-10 11:17:26.951 [10115] DEBUG ThreadPool.cpp:111 - thread pool init success

感谢大佬们看完我这丑陋的代码。。。

2068 次点击
所在节点    程序员
22 条回复
guang19
2022-05-10 11:23:51 +08:00
补充下:环境是 ubuntu wsl gcc 11.2.0 ,C++17
iOCZ
2022-05-10 11:35:27 +08:00
哎,C++真难看懂,就跟和尚的头皮点了香似的
guang19
2022-05-10 11:52:29 +08:00
我这样写也有问题:
````
auto asyncTask = ::std::make_shared<::std::packaged_task<RetType ()>>(
::std::bind(::std::forward<Fn>(func), ::std::forward<Args>(args)...));
auto future = asyncTask->get_future();
TaskType task = [asyncTask]() mutable
{
assert(asyncTask.use_count() == 1);
(*asyncTask)();
};
assert(asyncTask.use_count() == 2);
````

我发现这应该是 lambda 捕获 shared_ptr 引用错乱的问题,可能不是我代码的问题
Inn0Vat10n
2022-05-10 12:14:53 +08:00
inline 掉了吧,可以看看汇编代码
guang19
2022-05-10 12:40:34 +08:00
又 debug 了下,大概是我阻塞队列写的有点问题。。。
statumer
2022-05-10 12:50:06 +08:00
除非你已经定位到问题了,否则建议把代码贴全。你的 shared_ptr 被 task 捕获以后,task 如果被拷贝的话,shared_ptr 也会一块儿被复制。
elfive
2022-05-10 12:58:36 +08:00
我记得好像是 std::thread 有问题,资源不会及时释放,好像是会一直有一份拷贝存在。
后来我只好用 boost::thread 替换了,才解决这个问题。
guang19
2022-05-10 13:05:30 +08:00
@statumer 谢谢老哥,我大概知道为什么 use_count 为 3 了,在 shared_ptr 传参的过程中被复制了几次,所以造成引用次数不一致。有时候任务线程执行的快,被拷贝的 shared_ptr 来不及析构,lambda 此时执行的时候的 use_count 就是 3 ;任务线程执行的慢,等其他被拷贝的被析构了,只剩下 lambda 捕获的 shared_ptr 了,此时执行 use_count 就是 1 。我又测试了好多遍,虽然 use_count 不一致,但 task 执行的结果却没错,这证明了 lambda 只会是最后一个 shared_ptr ,lambda 结束后并不会造成泄漏。
guang19
2022-05-10 13:09:52 +08:00
@elfive 因为我写的这个库是基于 linux 的,所以自己基于 posix pthread 封装的线程,cpp11 的 thread 太难用了,跟坨屎一样,创建之后非要调 join 或 detach 才会执行,join 阻塞主线程就不说了,而 detach 更是难用。
codefun666
2022-05-10 15:19:02 +08:00
c++太复杂了,用 c 吧。

看到```::```就头疼...
hhhWhy
2022-05-10 15:37:52 +08:00
@guang19 没有看代码,但是这里有点不对,cpp11 的 thread 是在完成初始化之前运行的
hhhWhy
2022-05-10 15:39:21 +08:00
ColorfulBoar
2022-05-10 15:41:28 +08:00
@guang19 你这也太离谱了……谁教你的 join/detach 之后才会执行?真心建议重新学习一下标准库里面的 thread

constructor:
Effects: The new thread of execution executes...
Synchronization: The completion of the invocation of the constructor synchronizes with the beginning
of the invocation of the copy of f.

void join();
Effects: Blocks until the thread represented by *this has completed.
Synchronization: The completion of the thread represented by *this synchronizes with (6.9.2) the
corresponding successful join() return...

void detach();
Effects: The thread represented by *this continues execution without the calling thread blocking.
When detach() returns, *this no longer represents the possibly continuing thread of execution...
guang19
2022-05-10 15:50:11 +08:00
@ColorfulBoar 那请你帮个忙把这段代码贴到 linux 下去运行下,这个线程会不会执行,我的 archlinux 和 ubuntu 反正是不行的:
````
::std::thread t1([] ()
{
::printf("%s\n", "hello world");
});
::std::this_thread::sleep_for(::std::chrono::milliseconds(5000));
````
guang19
2022-05-10 16:04:04 +08:00
@ColorfulBoar 等 5 秒中都不执行的线程,非要手工指定 join 或 detach 状态的线程,你觉得离谱吗?
ColorfulBoar
2022-05-10 16:12:46 +08:00
@guang19 这……你不会以为 printf 是直接写屏幕上的所以不显示等于没执行吧?你这个在当前 scope 结束的时候 t1 仍然是 joinable 的,所以 destructor 会调用 std::terminate(),然后 stdout 的缓冲区里面的东西就直接被扔了所以看起来什么都没有。你关了 buffer 或者手动刷新一下就能看出来了。
guang19
2022-05-10 16:25:47 +08:00
@ColorfulBoar 惊了,学习了,谢谢大佬。
wzzzx
2022-05-10 21:17:19 +08:00
@ColorfulBoar #16 我有个疑问想请教一下,我知道在当前 scope 结束后,t1 仍然是 joinable 的,但这是为什么丫?为什么要这么设计?明明已经执行完毕了,为什么还需要额外去调用一下 join 来保证它不是 joinable 的呢?
wzzzx
2022-05-10 21:18:22 +08:00
@ColorfulBoar #16 https://en.cppreference.com/w/cpp/thread/thread/joinable
文档里也是这么说的
```
A thread that has finished executing code, but has not yet been joined is still considered an active thread of execution and is therefore joinable.
```
但是我不大理解这么设计的原因
kilasuelika
2022-05-10 21:48:07 +08:00
你的 std 前为啥要用::?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/851924

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX