C++ 处理多线程清理的最佳方法
我有一个服务器类型的应用程序,我有一个问题,确保线程在完成之前没有被删除。下面的代码几乎代表了我的服务器;需要进行清理以防止列表中死线的累积C++ 处理多线程清理的最佳方法,c++,multithreading,c++11,C++,Multithreading,C++11,我有一个服务器类型的应用程序,我有一个问题,确保线程在完成之前没有被删除。下面的代码几乎代表了我的服务器;需要进行清理以防止列表中死线的累积 using namespace std; class A { public: void doSomethingThreaded(function<void()> cleanupFunction, function<bool()> getStopFlag) { somethingThread = thread(
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;
}
使用名称空间std;
甲级{
公众:
void dosomethingthread(函数cleanupFunction,函数getStopFlag){
somethingThread=线程([cleanupFunction,getStopFlag,this](){
剂量测定(getStopFlag);
cleanupFunction();
});
}
私人:
无效剂量测定(函数getStopFlag);
穿一些东西阅读;
...
}
B类{
公众:
void runServer();
无效停止(){
stopFlag=true;
waitForListToBeEmpty();
}
私人:
void waitForListToBeEmpty(){…};
无效手浸接受(…){
共享_ptr newClient(newa());
{
唯一锁(listMutex);
clientData.push_back(newClient);
}
doSomethingthread(绑定(&B::cleanup,this,newClient),[this](){
返回停止标志;
});
}
空洞清理(共享的ptr数据){
唯一锁(listMutex);
clientData.remove(数据);
}
列出客户数据;
互斥列表互斥;
atomc停止标志;
}
问题似乎是析构函数以错误的顺序运行-即共享的_ptr在线程的函数完成时被析构函数,这意味着“A”对象在线程完成之前被删除,在调用线程的析构函数时导致havok
i、 e。
调用清除函数
所有对此(即一个对象)的引用都已删除,因此调用析构函数(包括该线程的析构函数)
再次调用此线程的析构函数--哦,不
我已经研究过一些替代方法,比如维护一个“待删除”列表,该列表定期被另一个线程用来清理主列表,或者对共享指针使用一个延时的deletor函数,但这两种方法看起来都有点笨重,可能会有争用条件
有人知道这样做的好方法吗?我找不到一种简单的方法来重构它,使其正常工作。线程是可连接的还是可分离的?我没有看到任何
分离,
这意味着在没有
这是一个致命的错误。你可以试着把它拆开,
尽管这会使干净的关机变得有些复杂。(关于
当然,对于很多服务器来说,永远不应该关闭
无论如何。)否则:我过去所做的就是创造
收割者线;一种只连接任何一条线的线
杰出的线程,清理后,他们
我可以补充一点,这是一个很好的例子
共享ptr不合适。你想完全控制一切
当删除发生时;如果分离,则可以在
清理功能(但坦率地说,只需使用删除此项;
在A::doSomethingthread
中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;
}
}
};
类重新排队
{
std::deque myQueue;
std::mutex myMutex;
std::条件_变量myCond;
A*getOne()
{
std::锁(myMutex);
myCond.wait(锁,[&](!myQueue.empty());
A*results=myQueue.front();
myQueue.pop_front();
返回结果;
}
公众:
void readyteau(A*成品螺纹)
{
std::唯一锁(myMutex);
myQueue.push_back(完成的_线程);
myCond.notify_all();
}
void reaperThread()
{
对于(;;)
{
A*mine=getOne();
mine->somethingThread.join();
删除地雷;
}
}
};
(警告:我还没有测试过这个,我尝试过使用C++11
功能性。我只是在过去实际实现了它,
使用pthreads,因此可能会出现一些错误
不过,原则应该成立。)
要使用,请创建一个实例,然后启动线程调用
reaperThread
。在清理每个线程时,调用
readyteau
要支持完全关闭,您可能需要使用两个队列:您
在创建第一个线程时将其插入第一个线程,然后
将其从第一个移动到第二个(对应于
readyteau
中的myQueue
,如上)。要关机,请等待
直到两个队列都为空(不在中启动任何新线程)
当然是这个时间间隔。问题在于,由于您通过共享指针管理A
,因此线程lambda捕获的this
指针实际上需要是一个共享指针,而不是原始指针,以防止其悬空。问题是,当您没有实际的共享ptr时,没有简单的方法从原始指针创建共享ptr
解决这个问题的一种方法是使用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类:公共启用\u共享\u{
公众:
void dosomethingthread(函数cleanupFunction,函数getStopFlag){
somethingThread=线程([cleanupFunction,getStopFlag,this](){
shared_ptr temp=来自_this()的共享_;
剂量测定(getStopFlag);
cleanupFunction();
});
这将为A
对象创建一个额外的共享ptr,使其在线程结束之前保持活动状态
请注意,James Kanze指出的join
/detach
仍然存在问题——每个线程在销毁前必须对其调用一次join
或detach
。如果不关心t线程退出值
如果在单个a
上多次调用doSomethingThreaded
,也可能出现问题
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;
}