处理多线程清理的最佳方法

时间:2014-04-03 15:41:50

标签: c++ multithreading c++11

我有一个服务器类型的应用程序,我有一个问题,确保线程在完成之前不会被删除。下面的代码几乎代表我的服务器;需要清理以防止在列表中建立死线程。

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函数,但这两个都是看起来很健壮,可能有竞争条件。

有人知道这样做的好方法吗?我看不出一种简单的方法来重构它以便正常工作。

3 个答案:

答案 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;
}