c ++线程池:用于将函数/ lambdas传递给线程的std :: function的替代方法?

时间:2017-11-10 18:58:30

标签: c++ multithreading function lambda pool

我有一个线程池,我用它来执行许多小工作(数百万个工作,每个几十个/几百毫秒)。这些工作以下列形式传递:

std::bind(&fn, arg1, arg2, arg3...)

[&](){fn(arg1, arg2, arg3...);}

让线程池像这样:

std::queue<std::function<void(void)>> queue;

void addJob(std::function<void(void)> fn)
{
    queue.emplace_back(std::move(fn));
}

非常标准的东西......除了我注意到瓶颈,如果作业在足够快的时间内执行(小于一毫秒),实际上在addJob函数中从lambda / binder到std :: function的转换花费的时间比执行工作本身要长。在做了一些阅读之后,std :: function非常慢,因此我的瓶颈并不一定是出乎意料的。

有没有更快的方法来做这种事情?我已经研究了drop-in std :: function替换,但它们或者与我的编译器不兼容或者速度不快。我也研究了Don Clugston的“快速代表”,但他们似乎不允许将参数与函数一起传递(也许我不能正确理解它们?)。

我正在使用VS2015u3进行编译,传递给作业的函数都是静态的,其参数是ints / floats或指向其他对象的指针。

1 个答案:

答案 0 :(得分:2)

为每种任务类型设置单独的队列 - 您可能没有数万个任务类型。这些中的每一个可以是例如任务的静态成员。然后addJob()实际上是任务的代理人,它是完全类型安全的。

然后定义任务类型的编译时列表,并通过模板元编程(for_each)访问它。它会更快,因为您不需要任何虚拟调用fnptr / std::function<>来实现此目的。

这只有在你的元组代码看到所有Task类时才会起作用(所以你不能通过从光盘加载图像来为已经运行的可执行文件添加一个新的Task后代 - 希望这是非-issue)。

template<typename D> // CRTP on D
class Task {
public:
    // you might want to static_assert at some point that D is in TaskTypeList

    Task() : it_(tasks_.end()) {} // call enqueue() in descendant

    ~Task() {
        // add your favorite lock here
        if (queued()) {
            tasks_.erase(it_);
        }
    }

    bool queued() const { return it_ != tasks_.end(); }

    static size_t ExecNext() {
        if (!tasks_.empty()) {
            // add your favorite lock here
            auto&& itTask = tasks_.begin();
            tasks_.pop_front();
            // release lock
            (*itTask)();
            itTask->it_ = tasks_.end();
        }
        return tasks_.size();
    }

protected:
    void enqueue() const
    {
        // add your favorite lock here
        tasks_.push_back(static_cast<D*>(this));
        it_ = tasks_.rbegin();
    }

private:
    std::list<D*>::iterator it_;

    static std::list<D*> tasks_; // you can have one per thread, too - then you don't need locking, but tasks are assigned to threads statically
};

struct MyTask : Task<MyTask> {
    MyTask() { enqueue(); } // call enqueue only when the class is ready
    void operator()() { /* add task here */ }
    // ...
};

struct MyTask2; // etc.

template<typename...>
struct list_ {};

using TaskTypeList = list_<MyTask, MyTask2>;

void thread_pocess(list_<>) {}

template<typename TaskType, typename... TaskTypes>
void thread_pocess(list_<TaskType, TaskTypes...>)
{
    TaskType::ExecNext();
    thread_process(list_<TaskTypes...>());
}

void thread_process(void*)
{
    for (;;) {
        thread_process(TaskTypeList());
    }
}

调整这段代码有很多不同之处:不同的线程应该从队列的不同部分开始(或者一个人使用一个环,或者几个队列以及静态/动态分配给线程),你&#39; d当完全没有任务时,将它发送到睡眠状态,可以有任务的枚举等等。

请注意,这不能与任意lambdas一起使用:您需要列出任务类型。你需要沟通&#39; lambda类型在你声明它的函数之外(例如通过返回`std :: make_pair(retval,list_),有时它不容易。但是,你总是可以将lambda转换为functor,这很简单 - 只是丑陋。