假设我有以下代码
#include <thread>
#include <iostream>
#include <atomic>
struct FooBase {
void start(){
run_condition_ = true;
t_ = std::thread([this](){
thread_handler();
});
}
virtual ~FooBase(){
run_condition_ = false;
if(t_.joinable())
t_.join();
}
protected:
virtual void thread_handler() = 0;
std::atomic_bool run_condition_{false};
private:
std::thread t_;
};
struct Foo : FooBase {
void thread_handler() override {
while(run_condition_){
std::cout << "Foo derived thread.." << std::endl;
}
}
};
int main(){
Foo f;
f.start();
getchar();
return 0;
}
这里我认为因为派生类Foo
的析构函数在FooBase
之前调用thread_handler
vtable查找在基类中发生如果线程尚未加入(仍在运行)当Foo
的析构函数完成时。由于FooBase::thread_handler
是纯粹的虚拟,我基本上保证了sigabort。
我该如何防范这个?我没有将thread_handler
作为纯虚拟
virtual void thread_handler(){}
但我很遗憾在基类本身如何防范这种情况,我可以在基类中实现一个join_thread接口并从每个派生类中调用它,但这看起来很麻烦。
答案 0 :(得分:4)
这里有两个问题,两者都不符合你所描述的内容。
您的主题仅在~FooBase()
中停止。这意味着如果Foo::thread_handler
读取或写入任何成员,它们将在线程停止之前从其下面被销毁。
你足够快地到达析构函数,start()
在thread_handler()
实际上在新线程上实际调用Foo
的时间{{1}被破坏 - 这将导致纯虚拟呼叫。
无论哪种方式,您都需要确保在销毁Foo
时,与thread_handler
相关的任何内容都已完成。这意味着来自FooBase
的每个派生类必须在其析构函数中具有:
run_condition_ = false;
if (t_.joinable()) {
t_join();
}
撇开这是不可行的,因为t_
是private
(你可以把它包装成protected
stop()
),它是不是如果所有派生类都需要做一些特殊的工作,这是一个尴尬的设计。您可以将FooBase
放入其自己的类中,该类只接受任意可调用的参数:
class joining_thread {
public:
joining_thread() = default;
~joining_thread() { stop(); }
bool running() const { return run_condition_.load(); }
template <typename... Args>
void start(Args&&... args) {
run_condition_ = true;
t_ = std::thread(std::forward<Args>(args)...);
}
void stop() {
run_condition_ = false;
if (t_.joinable()) t_.join();
}
private:
std::thread t_;
std::atomic_bool run_condition_{false};
};
然后你的Foo
可以将其作为会员:
class Foo {
public:
void start() {
t_.start([this]{
while (t_.running()) { ... }
});
}
private:
// just make me the last member, so it's destroyed first
joining_thread t_;
};
对于整个running()
事情,这仍然有点尴尬,但希望这个想法是有道理的。
答案 1 :(得分:1)
您描述的内容是不可能的。在构造对象后调用“start”。该对象在该阶段有效。您已经避免了在构造函数中调用虚函数的常见问题,这会导致问题。任何线程调用都隐含着一种称为memory barrier的东西,因此您可以依赖于新线程将从创建它时存在的内存视图开始的事实。任何存在的东西都没有改变,很好
您的问题(如另一个答案中所述)是您可以在线程完成之前退出并销毁对象(并且它是vtable)。
最简单的解决方法是使用packaged task。调用“获取”未来可确保在继续之前完成任务。请考虑以下代码
#include "stdafx.h"
#include <thread>
#include <iostream>
#include <atomic>
#include <future>
int main()
{
std::atomic<bool> stop{ false };
std::future<void> sync;
std::packaged_task<void()> task([&stop]()
{
while (!stop)
{
std::cout << "Running\n";
}
});
std::thread thread([&task]() {task();});
getchar();
stop = true;
task.get_future().get();
thread.join();
return 0;
}