C ++ 11의 스레드 풀링


131

관련 질문 :

C ++ 11 소개 :

부스트 소개 :


어떻게받을 수 있나요 스레드 풀로 작업을 보내 만들고 그들을 계속해서 또 다시 삭제하지 않고를? 이는 영구 스레드가 결합하지 않고 다시 동기화됨을 의미합니다.


다음과 같은 코드가 있습니다.

namespace {
  std::vector<std::thread> workers;

  int total = 4;
  int arr[4] = {0};

  void each_thread_does(int i) {
    arr[i] += 2;
  }
}

int main(int argc, char *argv[]) {
  for (int i = 0; i < 8; ++i) { // for 8 iterations,
    for (int j = 0; j < 4; ++j) {
      workers.push_back(std::thread(each_thread_does, j));
    }
    for (std::thread &t: workers) {
      if (t.joinable()) {
        t.join();
      }
    }
    arr[4] = std::min_element(arr, arr+4);
  }
  return 0;
}

각 반복마다 스레드를 작성하고 결합하는 대신 반복마다 작업자 스레드에 태스크를 전송하고 한 번만 작성하는 것을 선호합니다.


1
여기 에 관련된 질문과 대답이 있습니다.
didierc

1
tbb를 사용하는 것에 대해 생각했습니다 (인텔이지만 무료 및 오픈 소스이며 원하는 것을 정확히합니까 : 단순히 (재귀 적으로 나눌 수있는 작업을 제출하고 스레드에 대해 걱정하지 마십시오))?
Walter

2
이 FOSS 프로젝트는 스레드 풀 라이브러리를 만들려는 시도이며 원하는 경우 확인하십시오. -> code.google.com/p/threadpool11
Etherealone

TBB 사용에있어 무엇이 문제입니까?
Walter

답변:


84

C ++ Thread Pool Library, https://github.com/vit-vit/ctpl을 사용할 수 있습니다 .

그런 다음 작성한 코드를 다음으로 바꿀 수 있습니다

#include <ctpl.h>  // or <ctpl_stl.h> if ou do not have Boost library

int main (int argc, char *argv[]) {
    ctpl::thread_pool p(2 /* two threads in the pool */);
    int arr[4] = {0};
    std::vector<std::future<void>> results(4);
    for (int i = 0; i < 8; ++i) { // for 8 iterations,
        for (int j = 0; j < 4; ++j) {
            results[j] = p.push([&arr, j](int){ arr[j] +=2; });
        }
        for (int j = 0; j < 4; ++j) {
            results[j].get();
        }
        arr[4] = std::min_element(arr, arr + 4);
    }
}

원하는 수의 스레드를 얻을 수 있으며 반복에서 스레드를 반복해서 작성 및 삭제하지 않습니다.


11
이것이 답이되어야합니다. 단일 헤더, 읽기 쉽고 간단하며 간결하며 표준을 준수하는 C ++ 11 라이브러리. 훌륭한 일!
Jonathan H

@ vit-vit 함수를 사용하여 예제를 줄 수 있습니까? results[j] = p.push([&arr, j](int){ arr[j] +=2; });
Hani Goc

1
@HaniGoc 참조로 인스턴스를 캡처하십시오.
Jonathan H

@ vit-vit STL 버전을 개선하기 위해 풀 요청을 보냈습니다.
Jonathan H

@ vit-vit : 질문, 힌트 힌트로 해당 라이브러리의 관리자에게 문의하기가 어렵습니다.
einpoklum

83

이것은 내 답변에서 다른 유사한 게시물로 복사되었습니다. 도움이 될 수 있기를 바랍니다.

1) 시스템이 지원할 수있는 최대 스레드 수로 시작하십시오.

int Num_Threads =  thread::hardware_concurrency();

2) 효율적인 스레드 풀 구현을 위해 Num_Threads에 따라 스레드가 생성되면 새 스레드를 만들거나 오래된 스레드를 결합하지 않는 것이 좋습니다. 성능이 저하되고 응용 프로그램이 직렬 버전보다 느려질 수 있습니다.

각 C ++ 11 스레드는 무한 루프를 사용하여 기능을 수행하면서 새로운 작업이 계속 실행되기를 기다립니다.

이러한 기능을 스레드 풀에 연결하는 방법은 다음과 같습니다.

int Num_Threads = thread::hardware_concurrency();
vector<thread> Pool;
for(int ii = 0; ii < Num_Threads; ii++)
{  Pool.push_back(thread(Infinite_loop_function));}

3) Infinite_loop_function

이것은 작업 대기열을 기다리는 "while (true)"루프입니다.

void The_Pool:: Infinite_loop_function()
{
    while(true)
    {
        {
            unique_lock<mutex> lock(Queue_Mutex);

            condition.wait(lock, []{return !Queue.empty() || terminate_pool});
            Job = Queue.front();
            Queue.pop();
        }
        Job(); // function<void()> type
    }
};

4) 대기열에 작업을 추가하는 기능 만들기

void The_Pool:: Add_Job(function<void()> New_Job)
{
    {
        unique_lock<mutex> lock(Queue_Mutex);
        Queue.push(New_Job);
    }
    condition.notify_one();
}

5) 임의의 기능을 대기열에 바인딩

Pool_Obj.Add_Job(std::bind(&Some_Class::Some_Method, &Some_object));

이러한 구성 요소를 통합하면 고유 한 동적 스레딩 풀이 있습니다. 이 스레드는 항상 실행되어 작업을 기다립니다.

구문 오류가 있으면 죄송합니다. 이러한 코드를 입력하면 메모리가 부족합니다. 작업 무결성을 위반하는 완전한 스레드 풀 코드를 제공 할 수 없습니다.

편집 : 풀을 종료하려면 shutdown () 메소드를 호출하십시오.

XXXX::shutdown(){
{
    unique_lock<mutex> lock(threadpool_mutex);
    terminate_pool = true;} // use this flag in condition.wait

    condition.notify_all(); // wake up all threads.

    // Join all threads.
    for(std::thread &every_thread : thread_vector)
    {   every_thread.join();}

    thread_vector.clear();  
    stopped = true; // use this flag in destructor, if not set, call shutdown() 
}

thread (const thread &) = delete 일 때 어떻게 vector <thread>가 있습니까?
Christopher Pisz

1
@ChristopherPisz std::vector는 요소를 복사 할 필요가 없습니다. 당신은 이동 전용 유형 (과 벡터를 사용하여 unique_ptr, thread, future, 등).
Daniel Langr

위의 예에서 풀을 어떻게 중지합니까? 해야 condition.wait또한 변수를 찾아 stop_체크 if (stop_ == true) { break;}?
John

@John, 위의 종료 방법을 참조하십시오.
PhD AP EcE

2
shutdown ()에서 thread_vector.clear ()이어야합니다. thread_vector.empty () 대신에; 옳은?
sudheerbb

63

스레드 풀은 모든 스레드가 항상 실행 중임을 의미합니다. 다시 말해 스레드 함수는 절대 반환되지 않습니다. 스레드에 의미있는 작업을 제공하려면 스레드에 수행 할 작업이 있음을 알리고 실제 작업 데이터를 전달할 목적으로 스레드 간 통신 시스템을 설계해야합니다.

일반적으로 여기에는 일종의 동시 데이터 구조가 포함되며 각 스레드는 아마도 일종의 조건 변수에서 휴면 상태가 될 것입니다. 알림을 수신하면 하나 이상의 스레드가 깨어나 동시 데이터 구조에서 작업을 복구 한 후 처리하고 결과를 유사한 방식으로 저장합니다.

그런 다음 스레드는 더 많은 작업이 있는지 확인하고 다시 잠자 지 않으면 다시 확인합니다.

결론은 보편적으로 적용 할 수있는 "작업"이라는 자연스러운 개념이 없기 때문에이 모든 것을 직접 설계해야한다는 것입니다. 그것은 약간의 작업이며, 제대로 이해해야 할 미묘한 문제가 있습니다. (뒤에서 스레드 관리를 담당하는 시스템이 마음에 들면 Go에서 프로그래밍 할 수 있습니다.)


11
"당신은이 모든 것을 스스로 디자인해야한다"<-그것이 내가 피하려고하는 것이다. 하지만 고 루틴은 환상적입니다.
Yktula

2
@Yktula : 글쎄, 그것은 매우 사소한 작업입니다. 어떤 종류의 작업을 원하는지 게시물에서 명확하지 않으며 솔루션의 핵심입니다. C ++로 Go를 구현할 수 있지만 매우 구체적 일 것이며 절반의 사람들이 다른 것을 원한다고 불평 할 것입니다.
Kerrek SB

19

스레드 풀은 핵심적으로 이벤트 루프로 작동하는 함수에 바인딩 된 스레드 세트입니다. 이 스레드는 작업이 실행되거나 자체 종료 될 때까지 끝없이 기다립니다.

스레드 풀 작업은 작업을 제출하고 이러한 작업을 실행하는 정책 (스케줄 규칙, 스레드 인스턴스화, 풀 크기)을 정의 (및 수정)하고 스레드 및 관련 리소스의 상태를 모니터링하는 인터페이스를 제공하는 것입니다.

다재다능한 풀의 경우, 작업이 무엇인지, 어떻게 시작되고 중단되는지, 결과가 무엇인지 (해당 질문에 대한 약속과 미래의 개념 참조) 스레드가 응답해야 할 이벤트의 종류를 정의하여 시작해야합니다. , 그들이 어떻게 처리 할 것인지, 어떻게 이러한 사건들이 과제에 의해 처리되는 것과 구별되는지. 솔루션이 점점 더 복잡 해짐에 따라이 과정은 상당히 복잡해지며 스레드 작동 방식에 제한을 가할 수 있습니다.

현재 이벤트 처리 도구는 상당히 베어 본 (*)입니다. 뮤텍스, 조건 변수 및 그 위에 몇 가지 추상화 (잠금, 장벽)와 같은 기본 요소가 있습니다. 그러나 어떤 경우에는 이러한 폐색이 부적합한 것으로 판명 될 수 있습니다 (이 관련 질문을 참조하십시오) ), 기본 요소를 사용하도록 되돌려 야합니다.

다른 문제도 관리해야합니다.

  • 신호
  • 나는
  • 하드웨어 (프로세서 선호도, 이기종 설정)

이것들은 당신의 환경에서 어떻게 진행됩니까?

비슷한 질문에 대한 이 답변 은 부스트 ​​및 STL을위한 기존 구현을 가리 킵니다.

위에서 설명한 많은 문제를 다루지 않는 다른 질문에 대해 매우 복잡한 스레드 풀 구현 을 제공했습니다 . 당신은 그것에 구축 할 수 있습니다. 영감을 얻기 위해 다른 언어로 된 기존 프레임 워크를 살펴볼 수도 있습니다.


(*) 나는 그것을 반대로 문제로 보지 않습니다. 나는 그것이 C에서 상속받은 C ++의 정신이라고 생각합니다.


4
Follwoing [PhD EcE](https://stackoverflow.com/users/3818417/phd-ece) suggestion, I implemented the thread pool:

function_pool.h

#pragma once
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cassert>

class Function_pool
{

private:
    std::queue<std::function<void()>> m_function_queue;
    std::mutex m_lock;
    std::condition_variable m_data_condition;
    std::atomic<bool> m_accept_functions;

public:

    Function_pool();
    ~Function_pool();
    void push(std::function<void()> func);
    void done();
    void infinite_loop_func();
};

function_pool.cpp

#include "function_pool.h"

Function_pool::Function_pool() : m_function_queue(), m_lock(), m_data_condition(), m_accept_functions(true)
{
}

Function_pool::~Function_pool()
{
}

void Function_pool::push(std::function<void()> func)
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_function_queue.push(func);
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    lock.unlock();
    m_data_condition.notify_one();
}

void Function_pool::done()
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_accept_functions = false;
    lock.unlock();
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    m_data_condition.notify_all();
    //notify all waiting threads.
}

void Function_pool::infinite_loop_func()
{
    std::function<void()> func;
    while (true)
    {
        {
            std::unique_lock<std::mutex> lock(m_lock);
            m_data_condition.wait(lock, [this]() {return !m_function_queue.empty() || !m_accept_functions; });
            if (!m_accept_functions && m_function_queue.empty())
            {
                //lock will be release automatically.
                //finish the thread loop and let it join in the main thread.
                return;
            }
            func = m_function_queue.front();
            m_function_queue.pop();
            //release the lock
        }
        func();
    }
}

main.cpp

#include "function_pool.h"
#include <string>
#include <iostream>
#include <mutex>
#include <functional>
#include <thread>
#include <vector>

Function_pool func_pool;

class quit_worker_exception : public std::exception {};

void example_function()
{
    std::cout << "bla" << std::endl;
}

int main()
{
    std::cout << "stating operation" << std::endl;
    int num_threads = std::thread::hardware_concurrency();
    std::cout << "number of threads = " << num_threads << std::endl;
    std::vector<std::thread> thread_pool;
    for (int i = 0; i < num_threads; i++)
    {
        thread_pool.push_back(std::thread(&Function_pool::infinite_loop_func, &func_pool));
    }

    //here we should send our functions
    for (int i = 0; i < 50; i++)
    {
        func_pool.push(example_function);
    }
    func_pool.done();
    for (unsigned int i = 0; i < thread_pool.size(); i++)
    {
        thread_pool.at(i).join();
    }
}

2
감사! 이를 통해 병렬 스레딩 작업을 시작할 수있었습니다. 약간 수정 된 버전의 구현을 사용했습니다.
Robbie Capps 2014 년

3

이와 같은 것이 도움이 될 수 있습니다 (작동하는 앱에서 가져옴).

#include <memory>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

struct thread_pool {
  typedef std::unique_ptr<boost::asio::io_service::work> asio_worker;

  thread_pool(int threads) :service(), service_worker(new asio_worker::element_type(service)) {
    for (int i = 0; i < threads; ++i) {
      auto worker = [this] { return service.run(); };
      grp.add_thread(new boost::thread(worker));
    }
  }

  template<class F>
  void enqueue(F f) {
    service.post(f);
  }

  ~thread_pool() {
    service_worker.reset();
    grp.join_all();
    service.stop();
  }

private:
  boost::asio::io_service service;
  asio_worker service_worker;
  boost::thread_group grp;
};

다음과 같이 사용할 수 있습니다.

thread_pool pool(2);

pool.enqueue([] {
  std::cout << "Hello from Task 1\n";
});

pool.enqueue([] {
  std::cout << "Hello from Task 2\n";
});

효율적인 비동기식 큐 메커니즘 을 재창조하는 것은 쉬운 일 이 아닙니다.

Boost :: asio :: io_service는 매우 효율적인 구현이거나 실제로는 플랫폼 별 래퍼 모음입니다 (예 : Windows의 I / O 완료 포트를 래핑).


2
C ++ 11에서 많은 부스트가 필요합니까? 예를 들어 std::thread충분하지 않습니까?
einpoklum

에 상응 없습니다 std에 대한이 boost::thread_group. 인스턴스 boost::thread_group모음입니다 boost::thread. 그러나 물론, 그것은 대체하는 것은 매우 간단 boost::thread_group로모그래퍼 vectorstd::thread의.
rustyx

3

편집 : 이제 C ++ 17과 개념이 필요합니다. (9/12/16 현재 g ++ 6.0 이상이면 충분합니다.)

템플릿 공제는 그것 때문에 훨씬 더 정확하므로 새로운 컴파일러를 얻는 노력을 기울일 가치가 있습니다. 명시적인 템플릿 인수가 필요한 함수를 아직 찾지 못했습니다.

또한 적절한 호출 가능한 객체를 취합니다 ( 그리고 여전히 정적으로 타입 안전합니다 !!! ).

또한 동일한 API를 사용하는 선택적 녹색 스레딩 우선 순위 스레드 풀이 포함됩니다. 이 클래스는 POSIX 전용입니다. ucontext_t사용자 공간 작업 전환에 API를 사용합니다 .


이를 위해 간단한 라이브러리를 만들었습니다. 사용 예는 다음과 같습니다. (내가 직접 작성해야한다고 결정하기 전에 찾은 것 중 하나이기 때문에 이것에 대답하고 있습니다.)

bool is_prime(int n){
  // Determine if n is prime.
}

int main(){
  thread_pool pool(8); // 8 threads

  list<future<bool>> results;
  for(int n = 2;n < 10000;n++){
    // Submit a job to the pool.
    results.emplace_back(pool.async(is_prime, n));
  }

  int n = 2;
  for(auto i = results.begin();i != results.end();i++, n++){
    // i is an iterator pointing to a future representing the result of is_prime(n)
    cout << n << " ";
    bool prime = i->get(); // Wait for the task is_prime(n) to finish and get the result.
    if(prime)
      cout << "is prime";
    else
      cout << "is not prime";
    cout << endl;
  }  
}

async임의의 (또는 void) 반환 값과 임의의 (또는 없음) 인수를 가진 함수를 전달할 수 있으며 해당하는을 반환합니다 std::future. 결과를 얻으려면 (또는 작업이 완료 될 때까지 기다리십시오) 전화하십시오.get() 십시오) 미래에 하십시오.

여기 github에는 다음과 같습니다 https://github.com/Tyler-Hardin/thread_pool .


1
훌륭해 보이지만 vit-vit의 헤더와 비교하는 것이 좋습니다!
Jonathan H

1
@ Sh3ljohn, 그것을 본다면, API에서 기본적으로 같은 것으로 보입니다. vit-vit은 boost의 lockfree queue를 사용합니다. (그러나 내 목표는 특별히 std :: * 만 사용하는 것이 었습니다. lockfree 대기열을 직접 구현할 수는 있지만 힘들고 오류가 발생하기 쉽다고 가정합니다.) 또한 vit-vit에는 관련 .cpp가 없습니다. 자신이하는 일을 모르는 사람들에게 사용하기가 더 간단합니다. (예 : github.com/Tyler-Hardin/thread_pool/issues/1 )
Tyler

또한 지난 몇 시간 동안 포크 한 stl 전용 솔루션을 가지고 있으며 처음에는 공유 포인터를 사용하는 것보다 더 복잡해 보였지만 실제로는 핫 사이즈 조정을 올바르게 처리해야합니다.
Jonathan H

@ Sh3ljohn, 아, 나는 뜨거운 크기 조정을 보지 못했습니다. 좋습니다. 의도 된 사용 사례에 속하지 않기 때문에 걱정하지 않았습니다. (개인적으로 크기를 조정하고 싶은 경우는 생각할 수 없지만 상상력이 부족하기 때문일 수 있습니다.)
Tyler

1
사용 사례 : 서버에서 RESTful API를 실행 중이며 서비스를 완전히 종료하지 않고도 유지 관리 목적으로 리소스 할당을 일시적으로 줄여야합니다.
Jonathan H

3

이것은 매우 간단하고 이해하기 쉽고 사용하기 쉽고 C ++ 11 표준 라이브러리 만 사용하며 용도에 맞게 보거나 수정할 수있는 또 다른 스레드 풀 구현입니다. 스레드를 사용하려면 좋은 출발점이되어야합니다. 수영장 :

https://github.com/progschj/ThreadPool


3

부스트 라이브러리에서 thread_pool 을 사용할 수 있습니다 .

void my_task(){...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    boost::asio::thread_pool pool(threadNumbers);

    // Submit a function to the pool.
    boost::asio::post(pool, my_task);

    // Submit a lambda object to the pool.
    boost::asio::post(pool, []() {
      ...
    });
}

오픈 소스 커뮤니티에서 스레드 풀 을 사용할 수도 있습니다 .

void first_task() {...}    
void second_task() {...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    pool tp(threadNumbers);

    // Add some tasks to the pool.
    tp.schedule(&first_task);
    tp.schedule(&second_task);
}

1

STL 외부에 의존성이없는 스레드 풀은 전적으로 가능합니다. 최근 에 정확히 동일한 문제를 해결하기 위해 작은 헤더 전용 스레드 풀 라이브러리 를 작성했습니다 . 동적 풀 크기 조정 (런타임에 작업자 수 변경), 대기, 중지, 일시 정지, 재개 등을 지원합니다. 도움이 되셨기를 바랍니다.


github 계정을 삭제 한 것 같습니다 (또는 링크가 잘못되었습니다). 이 코드를 다른 곳으로 옮겼습니까?
rtpax

1
@rtpax 나는 repo를 옮겼습니다.
cantordust
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.