C ++ 11中的线程池


130

相关问题

关于C ++ 11:

关于Boost:


我如何获得一个线程池将任务发送到,而不是一遍又一遍地创建和删除它们?这意味着持久线程无需加入即可重新同步。


我有看起来像这样的代码:

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(它是Intel,但免费且开源,并且正是您想要的东西:您只需提交(递归可分割的)任务,而不必担心线程)?
Walter

2
这个FOSS项目是我尝试创建线程池库的尝试,如果需要,请检查它。-> code.google.com/p/threadpool11
Etherealone

使用tbb有什么问题?
Walter

Answers:


84

您可以使用C ++线程池库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库。做得好!
乔纳森·H

@ vit-vit您可以举一个带有功能的例子吗?你怎么推一类的成员函数results[j] = p.push([&arr, j](int){ arr[j] +=2; });
哈尼戈奇

1
@HaniGoc只是通过引用捕获实例。
乔纳森·H

@ vit-vit向您发送请求请求以改进STL版本。
乔纳森·H

@ vit-vit:很难联系该库的维护者提出问题,提示提示。
einpoklum

82

这是从我的答案复制到另一个非常相似的帖子,希望对您有所帮助:

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_ptrthreadfuture等)。
丹尼尔·兰格

在上面的示例中,如何停止游泳池?condition.wait还应该寻找变量stop_并检查if (stop_ == true) { break;}吗?
约翰·约翰(John

@John,请参阅上面的关闭方法。
博士AP EcE

2
在shutdown()中,它应该是thread_vector.clear();。而不是thread_vector.empty(); 正确?
sudheerbb

63

线程池意味着您所有的线程一直在运行–换句话说,线程函数永远不会返回。为了给线程有意义的事情,您必须设计一个线程间通信系统,既要告诉线程有事情要做,又要传达实际的工作数据。

通常,这将涉及某种并发数据结构,并且每个线程可能会休眠在某种条件变量上,并在有工作要做时通知该线程。收到通知后,一个或几个线程将唤醒,从并发数据结构中恢复任务,对其进行处理,并以类似的方式存储结果。

然后,线程将继续检查是否还有更多工作要做,如果还没有,请回到睡眠状态。

结果是您必须自己设计所有这一切,因为没有一种普遍适用的自然的“工作”概念。这需要大量的工作,并且您必须解决一些细微的问题。(如果您喜欢幕后负责线程管理的系统,则可以在Go中编程。)


11
“您必须自己设计所有这一切” <-这就是我要避免做的事情。但是,goroutines似乎很棒。
Yktula

2
@Yktula:好吧,这是一项非常重要的任务。从您的帖子中甚至还不清楚您要完成哪种工作,而这对于解决方案而言是至关重要的。您可以在C ++中实现Go,但这将是非常具体的事情,一半的人会抱怨他们想要不同的东西。
Kerrek SB 2013年

19

线程池是一组线程的核心,所有线程都绑定到充当事件循环的函数。这些线程将无休止地等待任务执行或它们自己终止。

线程池作业将提供一个接口来提交作业,定义(并可能修改)运行这些作业的策略(调度规则,线程实例化,池的大小),并监视线程和相关资源的状态。

因此,对于一个通用的池,必须先定义一个任务是什么,如何启动,中断它,结果是什么(请参阅该问题的承诺和未来概念),线程将要响应的事件类型。到如何处理这些事件,如何将这些事件与任务处理的事件区分开。如您所见,这可能会变得非常复杂,并且随着解决方案的参与越来越多,对线程的工作方式施加了限制。

当前用于处理事件的工具还算是准系统(*):诸如互斥体,条件变量之类的基元,以及诸如此类的一些抽象(锁,屏障)。但是在某些情况下,这些摘要可能变得不合适(请参阅此相关问题),并且必须恢复使用原语。

其他问题也必须解决:

  • 信号
  • 输入/输出
  • 硬件(处理器关联性,异构设置)

这些将如何在您的设置中播放?

这个类似问题的答案指出了用于boost和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
谢谢!这确实帮助我开始了并行线程操作。我最终使用了您的实现的稍作修改的版本。
罗比·卡普斯

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

没有stdfor的等效项boost::thread_groupboost::thread_groupboost::thread实例的集合。但当然,这是很容易更换boost::thread_groupvectorstd::thread秒。
rustyx

3

编辑:这现在需要C ++ 17和概念。(截至16年9月12日,仅g ++ 6.0+就足够了。)

但是,模板推导因此而更加准确,因此值得尝试使用较新的编译器。我尚未找到需要显式模板参数的函数。

现在,它也接受任何适当的可调用对象(并且仍然是静态类型安全的!!!)。

现在,它还包括使用相同API的可选绿色线程优先级线程池。此类仅是POSIX。它使用ucontext_tAPI进行用户空间任务切换。


我为此创建了一个简单的库。下面给出了用法示例。(我之所以回答,是因为这是我决定必须自己编写之前发现的东西之一。)

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() Future。

这是github:https : //github.com/Tyler-Hardin/thread_pool


1
看起来很棒,但与vit-vit的标头进行比较将非常棒!
乔纳森·H

1
@ Sh3ljohn,乍一看,似乎它们在API中基本相同。vit-vit使用boost的无锁队列,比我的好。(但是我的目标专门是仅使用std :: *来实现。我想我可以自己实现无锁队列,但这听起来很难并且容易出错。)而且,vit-vit并没有关联的.cpp,对于不知道自己在做什么的人更容易使用。(如github.com/Tyler-Hardin/thread_pool/issues/1
泰勒

他/她还有一个仅用于stl的解决方案,在过去的几个小时中,我一直在进行分叉,起初它看起来比您都复杂,并且在各处都有共享的指针,但这实际上是正确处理热大小调整所必需的。
乔纳森·H

@ Sh3ljohn,啊,我没有注意到正在调整大小。真好。我选择不担心它,因为它实际上不在预期的用例范围内。(我想不出要亲自调整大小的情况,但这可能是由于缺乏想象力。)
泰勒(Tyler

1
用例示例:您正在服务器上运行RESTful API,并且出于维护目的需要暂时减少资源分配,而无需完全关闭服务。
乔纳森H

3

这是另一个非常简单,易于理解和使用的线程池实现,仅使用C ++ 11标准库,可以针对您的使用进行查看或修改,如果您想使用线程,那么它应该是一个不错的入门者池:

https://github.com/progschj/ThreadPool


3

您可以从boost库使用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我确实移动了存储库-我更新了答案以反映这一点。
cantordust
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.