2017-11-06 6 views
0

대기중인 스레드가있는 스레드 풀이 Windows 응용 프로그램의 큐에 푸시 될 때까지 대기합니다.C++ : 스레드 풀 및 컨텍스트 전환 속도 저하

1000 개의 작업을 순차적으로 풀의 대기열에 1000 개의 작업을 추가하는 기본 응용 프로그램 스레드에 루프가 있습니다 (작업을 추가 한 다음 작업이 완료 될 때까지 대기 한 후 다른 작업 x1000을 추가 함). 실제 병렬 처리는 발생하지 않습니다 그래서 ... 여기에 몇 가지 의사 코드입니다 :

////threadpool: 
class ThreadPool 
{ 
    .... 

    std::condition_variable job_cv; 
    std::condition_variable finished_cv; 
    std::mutex job_mutex; 
    std::queue<std::function <void(void)>> job_queue; 

    void addJob(std::function <void(void)> jobfn) 
    { 
     std::unique_lock <std::mutex> lock(job_mutex); 
     job_queue.emplace(std::move(jobfn)); 
     job_cv.notify_one(); 
    } 

    void waitForJobToFinish() 
    { 
     std::unique_lock<std::mutex> lock(job_mutex); 
     finished_cv.wait(lock, [this]() {return job_queue.empty(); }); 
    } 

    .... 

    void threadFunction() //called by each thread when it's first started 
    { 
     std::function <void(void)> job; 
     while (true) 
     { 
      std::unique_lock <std::mutex> latch(job_mutex); 
      job_cv.wait(latch, [this](){return !job_queue.empty();}); 

      { 
       job = std::move(job_queue.front()); 
       job_queue.pop(); 

       latch.unlock(); 

       job(); 

       latch.lock(); 
       finished_cv.notify_one(); 
      }  
     } 
    } 
} 

...

////main application: 

void jobfn() 
{ 
    //do some lightweight calculation 
} 

void main() 
{ 
    //test 1000 calls to the lightweight jobfn from the thread pool 
    for (int q = 0; q < 1000; q++) 
    {   
     threadPool->addJob(&jobfn); 
     threadPool->waitForJobToFinish(); 
    } 
} 
그래서 기본적으로 작업이 큐와 메인 루프에 추가됩니다 무슨 일이 일어나고 있는지 기다려야하기 시작

기다리는 쓰레드는 그것을 집어 내고, 쓰레드가 끝나면 애플리케이션은 메인 루프가 계속되고 또 다른 작업이 큐에 추가 될 수 있음을 알린다. 그런 식으로 1000 개의 작업이 순차적으로 처리된다.

작업 자체는 작고 몇 밀리 초 만에 완료 할 수 있습니다.

그러나, 나는 뭔가 이상한 ....

가 완료 될 때까지 루프 걸리는 시간은 기본적으로 n은 스레드 풀의 스레드 수입니다 O (N)입니다 나타났습니다. 따라서 모든 시나리오에서 작업이 한 번에 하나씩 처리 되더라도 10 스레드 풀은 1 스레드 풀보다 전체 1000 작업을 완료하는 데 10 배 더 오래 걸립니다.

이유를 알아 내려고하고 있습니다. 내 유일한 추측은 컨텍스트 스위칭이 병목 현상 ... 어쩌면 덜 (또는 0일까요?) 컨텍스트 스위칭 오버 헤드는 오직 하나의 스레드가 잡을 때 필요합니다. 그러나 10 개의 스레드가 한 번에 하나의 작업을 처리하기 위해 계속해서 돌고있을 때 추가 처리가 필요합니까? 하지만 그건 나에게 의미가 없다 ... 스레드 B, C, D처럼 스레드 A의 작업을 잠금 해제하는 데 필요한 동일한 작업이 아닌가? 거기에 어떤 스레드가 다른 스레드가 주어질 때까지 상황을 잃지 않는 OS 레벨 캐싱이 있습니까? 따라서 동일한 스레드를 반복해서 호출하면 스레드 A, B, C를 순차적으로 호출하는 것보다 빠릅니까?

하지만이 시점에서 완전히 추측 할 수 있습니다. 어쩌면 다른 사람들이 왜 이러한 결과를 얻는 지에 대한 통찰력을 얻을 수 있습니다. 직관적으로 나는 단 한 번에 하나의 스레드 만 실행되는 동안 임의로 많은 수의 스레드를 가진 스레드 풀을 가질 수 있으며 [x] 작업의 총 작업 완료 시간은 동일합니다 (각 작업이 동일하고 총 작업 수가 동일하면 ...) 이유는 무엇입니까 그게 잘못된거야?

+0

이것은 관련이 없지만 몇 개의 코어가 있습니까? – merlin2011

+0

@ merlin2011 16 (2 CPU x 8 코어). – Tyson

+0

또한 어떻게 벤치마킹합니까? 즉, 시작과 끝에서 시간을 측정하거나 각 작업에 대한 데이터를 수집하여 천천히 매 10 초마다 1이되는지 여부를 알 수 있습니까? – merlin2011

답변

0

"추측"이 정확합니다. 단순히 자원 경합 문제 일뿐입니다.

10 개의 스레드가 유휴 상태가 아니며 대기 중입니다. 이는 OS가 현재 사용중인 응용 프로그램 스레드를 반복해야한다는 것을 의미합니다. 즉, 컨텍스트 스위치가 발생할 가능성이 높습니다.

활성 스레드가 뒤로 밀리면 "대기중인"스레드가 앞으로 당겨집니다. 코드에서 신호가 통지되고 잠금을 획득 할 수 있는지 확인합니다. 그 스레드는 잠금을 획득 할 수 있는지를 알아 내려고하는 나머지 스레드에 대해 반복 작업을 계속합니다. "활성"스레드가 아직 완료 할 시간 조각을 할당받지 않았기 때문에 잠금을 얻을 수 없습니다.

단일 스레드 풀에는 OS 수준에서 추가 스레드를 반복 할 필요가 없으므로이 문제가 없습니다. 단일 스레드 풀은 여전히 ​​job 1000 번을 호출하는 것보다 느립니다.

희망이 도움이 될 수 있습니다.