2017-09-04 1 views
1

내 작업자 [스레드] 클래스에 일시 중지/다시 시작 기능을 추가하려는 시도에서 설명 할 수없는 문제가 발생했습니다. (C++ 1y/VS2015)작업자 스레드 일시 중단/재개 구현

문제는 교착 상태처럼 보입니다. 그러나 일단 디버거가 연결되고 특정 지점 (# 1 참조) 전에 중단 점이 설정되면이를 재현 할 수 없습니다. 그것은 타이밍 문제입니다.

뮤텍스를 길게 잡고 클라이언트 코드가 다른 뮤텍스를 획득하려고 시도 할 수 있기 때문에 내가 (# 2) 찾을 수있는 수정 사항이 많은 의미를 갖지 않는다. 교착 상태의 가능성을 높입니다.

하지만 문제가 해결됩니다.

작업자 루프 :

Job* job; 
while (true) 
{ 
    { 
    std::unique_lock<std::mutex> lock(m_jobsMutex); 
    m_workSemaphore.Wait(lock); 

    if (m_jobs.empty() && m_finishing) 
    { 
     break; 
    } 

    // Take the next job 
    ASSERT(!m_jobs.empty()); 
    job = m_jobs.front(); 
    m_jobs.pop_front(); 
    } 

    bool done = false; 
    bool wasSuspended = false; 
    do 
    { 
    // #2 
    { // Removing this extra scoping seemingly fixes the issue BUT 
     // incurs us holding on to m_suspendMutex while the job is Process()ing, 
     // which might 1, be lengthy, 2, acquire other locks. 
     std::unique_lock<std::mutex> lock(m_suspendMutex); 
     if (m_isSuspended && !wasSuspended) 
     { 
     job->Suspend(); 
     } 
     wasSuspended = m_isSuspended; 

     m_suspendCv.wait(lock, [this] { 
     return !m_isSuspended; 
     }); 

     if (wasSuspended && !m_isSuspended) 
     { 
     job->Resume(); 
     } 
     wasSuspended = m_isSuspended; 
    } 

    done = job->Process(); 
    } 
    while (!done); 
} 

가 일시 중단/다시 시작은 다음과 같습니다

void Worker::Suspend() 
{ 
    std::unique_lock<std::mutex> lock(m_suspendMutex); 
    ASSERT(!m_isSuspended); 
    m_isSuspended = true; 
} 

void Worker::Resume() 
{ 
    { 
    std::unique_lock<std::mutex> lock(m_suspendMutex); 
    ASSERT(m_isSuspended); 
    m_isSuspended = false; 
    } 
    m_suspendCv.notify_one(); // notify_all() doesn't work either. 
} 

(비주얼 스튜디오) 테스트 :/잘못하고

내가 놓친 게 무엇
struct Job: Worker::Job 
    { 
    int durationMs = 25; 
    int chunks = 40; 
    int executed = 0; 

    bool Process() 
    { 
     auto now = std::chrono::system_clock::now(); 
     auto until = now + std::chrono::milliseconds(durationMs); 
     while (std::chrono::system_clock::now() < until) 
     { /* busy, busy */ 
     } 

     ++executed; 
     return executed < chunks; 
    } 

    void Suspend() { /* nothing here */ } 
    void Resume() { /* nothing here */ } 
    }; 

    auto worker = std::make_unique<Worker>(); 

    Job j; 
    worker->Enqueue(j); 

    std::this_thread::sleep_for(std::chrono::milliseconds(j.durationMs)); // Wait at least one chunk. 

    worker->Suspend(); 

    Assert::IsTrue(j.executed < j.chunks); // We've suspended before we finished. 
    const int testExec = j.executed; 

    std::this_thread::sleep_for(std::chrono::milliseconds(j.durationMs * 4)); 

    Assert::IsTrue(j.executed == testExec); // We haven't moved on. 

    // #1 
    worker->Resume(); // Breaking before this call means that I won't see the issue. 
    worker->Finalize(); 

    Assert::IsTrue(j.executed == j.chunks); // Now we've finished. 

? 왜 작업의 프로세스()는 suspend 뮤텍스에 의해 지켜 져야합니까?

EDIT : Resume()은 알림시 뮤텍스를 유지하지 않아야합니다. 문제가 해결되지 않았습니다.

답변

0

물론 Process()suspend 뮤텍스로 보호 할 필요가 없습니다.

j.executed의 액세스 - 어설 션 및 증분에 대한 액세스는 동기화해야합니다 (std::atomic<int>으로 만들거나 뮤텍스 등으로 지키면 됨).

(메인 스레드의 변수에 쓰고 있지 않기 때문에) 문제가 어떻게 나타나는지 아직 명확하지 않습니다. undefined behaviour propagating backwards in time의 사례 일 수 있습니다.