2014-04-03 7 views
2

서버 유형 응용 프로그램이 있으며 스레드가 완료되기 전에 스레드가 삭제되지 않도록하는 데 문제가 있습니다. 아래 코드는 내 서버를 대표합니다. 목록에서 죽은 스레드가 쌓이지 않도록하려면 정리가 필요합니다. 하복 (Havok)를 일으키는 원인이되는 shared_ptr의이 스레드가 완료되기 전에 삭제됩니다 'A'개체를 의미 스레드의 기능이 완료되면에서 파괴되는, 즉 때 스레드의 -다중 스레드 정리를 처리하는 가장 좋은 방법

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

문제는 소멸자가 잘못된 순서로 실행하는 것이 보인다 소멸자가 호출됩니다.

즉 전화 정리 기능이에 대한 모든 참조 (즉는 A 객체) 제거, 그래서 다시 호출이 스레드의 소멸자 (이 스레드의 소멸자 포함) 소멸자를 호출 - OH NOES!

다른 스레드에서 기본 목록을 정리하거나 공유 포인터에 대해 시간 지연 삭제 기능을 사용하기 위해 주기적으로 사용되는 '제거 대상'목록을 유지 관리하는 것과 같은 대안을 살펴 보았습니다. 이들은 abit chunky처럼 보이고 경쟁 조건을 가질 수 있습니다.

누구든지 좋은 방법을 알고 있습니까? 나는 그것이 잘 작동하도록 리팩토링하는 쉬운 방법을 볼 수 없다.

답변

3

스레드가 결합 또는 분리되어 있습니까? detach, 이 표시되지 않습니다. 즉, 이 없어도 스레드 개체를 파괴하면 치명적인 오류가 발생합니다. 을 간단히 분리 해 볼 수도 있지만, 이는 다소 복잡한 문제를 일으킬 수 있습니다. (많은 서버의 경우 코스 중, 어쨌든 종료 이 없어야합니다.) 그렇지 않은 경우 : 과거에 내가 수행 한 작업은 리퍼 스레드를 만드는 것입니다. 어떤 작업도 수행하지 않고 어떤 개의 미해결 스레드에 합류하는 스레드가 그 뒤에 정리합니다.

shared_ptr이 이 아니고이 아닌 경우의 좋은 예라고 덧붙일 수 있습니다. 삭제가 발생하면 을 완전히 제어하고 싶습니다. 분리하는 경우 클린업 기능으로 처리 할 수 ​​있습니다. (솔직히 말해 을 람다의 끝에 사용하면 A::doSomethingThreaded은 더 많은 것으로 보입니다 ); 그렇지 않으면 합류 한 후 사신 스레드에서 수행합니다.

편집 : 사신 스레드에 대한

는 다음과 같이 작동합니다 :

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에서. 종료하려면 두 큐가 모두 비워 질 때까지 을 기다리십시오. 물론이 간격이 인 새 스레드를 시작하지 마십시오.

+0

몇 가지 예를 제공 할 수 있습니까? – 4pie0

+0

@lizusek 예를 들어 무엇을 원하십니까? 여러 가지 해결책을 제안했습니다. –

+0

사신 스레드를 만드는 방법의 예. 아무것도하지 않고 미해결의 thread에 합류 해 그 thread를 클린 업하는 thread. – 4pie0

1

공유 포인터를 통해 A을 관리하므로 스레드 람다에 의해 캡처 된 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의를 생성합니다. 당신은 여전히 ​​제임스 간제 식별 join/detach의 문제가

주 - 모든 스레드 해야join 또는 detach 중 하나가 정확히 한 번 파괴되기 전에 호출합니다. 스레드 출구 값을 전혀 신경 쓰지 않으면 스레드 lambda에 detach 호출을 추가하여 해당 요구 사항을 충족시킬 수 있습니다. doSomethingThreaded는 하나의 A 개체에서 여러 번 호출되는 경우 또한 문제에 대한 가능성이

... 관심이있는 사람들을 위해

0

, 나는 (즉 제임스의 분리 제안 주어진 두 답변의 ABIT했다, 크리스 'shared_ptr에 대한 제안).

내 결과 코드는 다음과 같습니다과 깔끔한 ​​보인다 종료 또는 클라이언트 해제에 충돌이 발생하지 않습니다

네임 스페이스를 사용하여 표준;

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; 
} 
관련 문제