繁体   English   中英

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

[英]c++ thread pool: alternative to std::function for passing functions/lambdas to threads?

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

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或指向其他对象的指针。

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

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

这只有在您的元组代码看到所有Task类时才会起作用(因此您不能通过从光盘加载图像来向已经运行的可执行文件添加Task的新后代 - 希望这不是问题)。

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());
    }
}

调整这段代码有很多东西:不同的线程应该从队列的不同部分开始(或者一个人会使用一个环,或者几个队列以及静态/动态分配给线程),当有空的时候你会把它发送到睡眠状态。绝对没有任务,可以有任务的枚举等。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM