如题,我正在用 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
感谢大佬们看完我这丑陋的代码。。。
1
guang19 OP 补充下:环境是 ubuntu wsl gcc 11.2.0 ,C++17
|
2
iOCZ 2022-05-10 11:35:27 +08:00
哎,C++真难看懂,就跟和尚的头皮点了香似的
|
3
guang19 OP 我这样写也有问题:
```` 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 引用错乱的问题,可能不是我代码的问题 |
4
Inn0Vat10n 2022-05-10 12:14:53 +08:00
inline 掉了吧,可以看看汇编代码
|
5
guang19 OP 又 debug 了下,大概是我阻塞队列写的有点问题。。。
|
6
statumer 2022-05-10 12:50:06 +08:00
除非你已经定位到问题了,否则建议把代码贴全。你的 shared_ptr 被 task 捕获以后,task 如果被拷贝的话,shared_ptr 也会一块儿被复制。
|
7
elfive 2022-05-10 12:58:36 +08:00 via iPhone
我记得好像是 std::thread 有问题,资源不会及时释放,好像是会一直有一份拷贝存在。
后来我只好用 boost::thread 替换了,才解决这个问题。 |
8
guang19 OP @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 结束后并不会造成泄漏。
|
9
guang19 OP @elfive 因为我写的这个库是基于 linux 的,所以自己基于 posix pthread 封装的线程,cpp11 的 thread 太难用了,跟坨屎一样,创建之后非要调 join 或 detach 才会执行,join 阻塞主线程就不说了,而 detach 更是难用。
|
10
codefun666 2022-05-10 15:19:02 +08:00
c++太复杂了,用 c 吧。
看到```::```就头疼... |
12
hhhWhy 2022-05-10 15:39:21 +08:00
|
13
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... |
14
guang19 OP @ColorfulBoar 那请你帮个忙把这段代码贴到 linux 下去运行下,这个线程会不会执行,我的 archlinux 和 ubuntu 反正是不行的:
```` ::std::thread t1([] () { ::printf("%s\n", "hello world"); }); ::std::this_thread::sleep_for(::std::chrono::milliseconds(5000)); ```` |
15
guang19 OP @ColorfulBoar 等 5 秒中都不执行的线程,非要手工指定 join 或 detach 状态的线程,你觉得离谱吗?
|
16
ColorfulBoar 2022-05-10 16:12:46 +08:00
@guang19 这……你不会以为 printf 是直接写屏幕上的所以不显示等于没执行吧?你这个在当前 scope 结束的时候 t1 仍然是 joinable 的,所以 destructor 会调用 std::terminate(),然后 stdout 的缓冲区里面的东西就直接被扔了所以看起来什么都没有。你关了 buffer 或者手动刷新一下就能看出来了。
|
17
guang19 OP @ColorfulBoar 惊了,学习了,谢谢大佬。
|
18
wzzzx 2022-05-10 21:17:19 +08:00
@ColorfulBoar #16 我有个疑问想请教一下,我知道在当前 scope 结束后,t1 仍然是 joinable 的,但这是为什么丫?为什么要这么设计?明明已经执行完毕了,为什么还需要额外去调用一下 join 来保证它不是 joinable 的呢?
|
19
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. ``` 但是我不大理解这么设计的原因 |
20
kilasuelika 2022-05-10 21:48:07 +08:00 via Android 2
你的 std 前为啥要用::?
|
21
nightwitch 2022-05-11 00:01:21 +08:00 1
在多线程环境下不要用 shared_ptr 的 use_count()以及 uniqiue() (已经在 C++20 被删除)这个 API 。
cppreference 上明确标记了这两个 API 不保证线程安全,所以在多线程环境下其只返回一个可能的值,不是精确的。 reference:https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count @wzzzx 好问题,除了不会自动 join 以外,std::thread 也不能被打断或者取消,这个问题在 C++20 得到了修复。标准库加入了 std::jthread ,见 https://en.cppreference.com/w/cpp/thread/jthread |
22
ColorfulBoar 2022-05-11 01:07:11 +08:00
@wzzzx joinable()只是检测这个 std::thread object 是不是依然持有一个进程而已别的啥都没干,或许你想问的是为什么 std::thread 的 destructor 这么奇怪……当然不管想问的是啥,不管把标准还是实现重复一遍肯定都没意思,你一定想听个刺激的,所以让我恶意转述一下史官 Bjarne Stroustrup 的说法:我们当年也不想这样,但活在 [世界上只有 C 语言 + POSIX 的 P 意思是 portable] 这个梦里的 C 标准委员会因为 C 里面没有 RAII / POSIX 天生残疾就以为别人也不行,强烈反对我们干人事(这是唯一一份 C 标准委员会发给 C++标准委员会的正式通知),那就别怪我们摆烂了。
|