我有一个服务器类型的应用程序,我有一个问题,确保线程在完成之前不会被删除。下面的代码几乎代表我的服务器;需要清理以防止在列表中建立死线程。
using namespace std;
class A {
public:
void doSomethingThreaded(function<void()> cleanupFunction, function<bool()> getStopFlag) {
somethingThread = thread([cleanupFunction, getStopFlag, this]() {
doSomething(getStopFlag);
cleanupFunction();
});
}
private:
void doSomething(function<bool()> getStopFlag);
thread somethingThread;
...
}
class B {
public:
void runServer();
void stop() {
stopFlag = true;
waitForListToBeEmpty();
}
private:
void waitForListToBeEmpty() { ... };
void handleAccept(...) {
shared_ptr<A> newClient(new A());
{
unique_lock<mutex> lock(listMutex);
clientData.push_back(newClient);
}
newClient.doSomethingThreaded(bind(&B::cleanup, this, newClient), [this]() {
return stopFlag;
});
}
void cleanup(shared_ptr<A> data) {
unique_lock<mutex> lock(listMutex);
clientData.remove(data);
}
list<shared_ptr<A>> clientData;
mutex listMutex;
atomc<bool> stopFlag;
}
问题似乎是析构函数以错误的顺序运行 - 即在线程函数完成时对shared_ptr进行破坏,这意味着在线程完成之前删除了'A'对象,在调用线程的析构函数时导致havok。
即。 调用清理功能 删除了对此的所有引用(即A对象),因此调用析构函数(包括此线程的析构函数) 再次调用此线程的析构函数 - OH NOES!
我已经查看了替代方法,例如维护一个'要删除'列表,该列表定期用于通过另一个线程清理主列表,或者对共享指针使用时间延迟的deletor函数,但这两个都是看起来很健壮,可能有竞争条件。
有人知道这样做的好方法吗?我看不出一种简单的方法来重构它以便正常工作。
答案 0 :(得分:3)
线程可以连接还是分离?我没有看到任何detach
,
这意味着在没有的情况下破坏线程对象
加入它是一个致命的错误。您可以尝试简单地分离它,
虽然这可以使干净的关闭有些复杂。 (的
当然,对于很多服务器来说,永远不应该关机
无论如何。)否则:我过去所做的就是创造
收割者的线索;除了加入任何东西之外什么都不做的线程
优秀的线程,在他们之后清理。
我可以补充一点,这是一个很好的例子
shared_ptr 不是合适的。你想要完全控制
删除发生时;如果你分开,你可以在
清理功能(但坦率地说,只使用delete this;
在A::doSomethingThreaded
中的lambda的末尾似乎更多
可读);否则,你在加入之后就去做了
收割者线程。
编辑:
对于收割者线程,以下内容应该有效:
class ReaperQueue
{
std::deque<A*> myQueue;
std::mutex myMutex;
std::conditional_variable myCond;
A* getOne()
{
std::lock<std::mutex> lock( myMutex );
myCond.wait( lock, [&]( !myQueue.empty() ) );
A* results = myQueue.front();
myQueue.pop_front();
return results;
}
public:
void readyToReap( A* finished_thread )
{
std::unique_lock<std::mutex> lock( myMutex );
myQueue.push_back( finished_thread );
myCond.notify_all();
}
void reaperThread()
{
for ( ; ; )
{
A* mine = getOne();
mine->somethingThread.join();
delete mine;
}
}
};
(警告:我没有对此进行过测试,我已尝试使用C ++ 11 功能。我过去只实际实现了它, 使用pthreads,所以可能会有一些错误。基础的 但是,原则应该成立。)
使用,创建一个实例,然后启动线程调用
reaperThread
就可以了。在每个线程的清理中,调用
readyToReap
。
要支持干净关闭,您可能需要使用两个队列:您
将每个线程插入到第一个线程中,然后再创建它
将它从第一个移动到第二个(这将对应于
myQueue
中的readyToReap
,上面的{{1}}。关闭,然后等待
直到两个队列都为空(没有开始任何新线程)
这个间隔,当然)。
答案 1 :(得分:1)
问题在于,由于您通过共享指针管理A
,因此线程lambda捕获的this
指针实际上需要是一个共享指针,而不是一个原始指针,以防止它变成悬空。问题是当你没有实际的shared_ptr时,没有简单的方法可以从原始指针创建shared_ptr。
解决此问题的一种方法是使用shared_from_this
:
class A : public enable_shared_from_this<A> {
public:
void doSomethingThreaded(function<void()> cleanupFunction, function<bool()> getStopFlag) {
somethingThread = thread([cleanupFunction, getStopFlag, this]() {
shared_ptr<A> temp = shared_from_this();
doSomething(getStopFlag);
cleanupFunction();
});
这会为A
对象创建一个额外的shared_ptr,使其保持活动状态直到线程完成。
请注意,James Kanze确定的join
/ detach
仍然存在问题 - 每个主题必须具有{{1}或者join
在它被销毁之前只调用了一次。如果您从不关心线程退出值,则可以通过向线程lambda添加detach
调用来满足该要求。
如果在单个detach
对象上多次调用doSomethingThreaded
,您也可能会遇到问题...
答案 2 :(得分:0)
对于那些感兴趣的人,我采取了两个答案(即詹姆斯的分离建议和克里斯关于shared_ptr的建议)。
我的结果代码看起来像这样,看起来更整洁,不会导致关机或客户端断开连接崩溃:
使用namespace std;
class A {
public:
void doSomething(function<bool()> getStopFlag) {
...
}
private:
...
}
class B {
public:
void runServer();
void stop() {
stopFlag = true;
waitForListToBeEmpty();
}
private:
void waitForListToBeEmpty() { ... };
void handleAccept(...) {
shared_ptr<A> newClient(new A());
{
unique_lock<mutex> lock(listMutex);
clientData.push_back(newClient);
}
thread clientThread([this, newClient]() {
// Capture the shared_ptr until thread over and done with.
newClient->doSomething([this]() {
return stopFlag;
});
cleanup(newClient);
});
// Detach to remove the need to store these threads until their completion.
clientThread.detach();
}
void cleanup(shared_ptr<A> data) {
unique_lock<mutex> lock(listMutex);
clientData.remove(data);
}
list<shared_ptr<A>> clientData; // Can remove this if you don't
// need to connect with your clients.
// However, you'd need to make sure this
// didn't get deallocated before all clients
// finished as they reference the boolean stopFlag
// OR make it a shared_ptr to an atomic boolean
mutex listMutex;
atomc<bool> stopFlag;
}