具有任意作业参数的无锁作业队列

时间:2015-02-16 21:11:35

标签: c++ multithreading cross-platform

目前,我正在使用Microsoft的Concurrency::concurrent_queue类来跨核心分发工作。工作线程从此队列中拉出工作。我有多个职业生产者(但如果真的有必要,我可以和一个生产者一起生活)。

目前,作业对象的实现方式如下:

struct Job
{
    void(*function_pointer)(void*); // Job function (used to execute the task)
    void* arguments;    // Job arguments (gets passed to the job function)
};

正如您所看到的,此系统要求作业生成者为每个作业的参数分配内存并保持分配直到作业完成。这正是我想要摆脱的,因为它很烦人且容易出错。当然,我可以用void* arguments;之类的内容替换int argument;。但我的工作有任意数量和类型的论据。

一个简单的解决方案是用这样的东西替换void* arguments;

char* arguments;    // Points to byte buffer that contains the arguments.

然后通过调用new(在作业完成时自动删除)为每个作业的参数分配内存。虽然这可行,但每个工作的分配对绩效不利 我想过使用循环缓冲区(用于存储所有作业的回调和参数),但我找不到任何可以无锁方式处理任意大小元素的实现。

简而言之,我希望能够做到这样的事情:

struct Job_arguments
{
    int argument1;
    char argument2;
    float argument3;
};
Job_arguments arguments(1, 2, 3.0f);
job_system->add_job(&callback, arguments); // Arguments get copied.

我希望我的问题的解释清楚。如果没有,我可以详细说明。

修改
对不起,如果我不清楚。我试图摆脱Concurrency::concurrent_queue来替换它导致没有分配的东西(比如循环缓冲区)。还应该以不会导致分配的方式存储作业。

1 个答案:

答案 0 :(得分:3)

你见过std::function<void()>吗?

它会删除任何可调用的对象。要捆绑参数,只需创建一个lambda [=]{ some_function( some_arguments ); }并将其传递给std::function<void()>

为了提高效率,请进入,只进行一次分配。希望try_pop写得合理,并将目的地移入。

使用[=]进行捕获,因为[&]捕获是危险的。如果需要缓冲区,可以轻松[=]复制指针,然后手动删除(危险且容易出错),使用C ++ 14样式捕获列表和拥有缓冲区([buff=std::move(buff),=]),或写入你自己的自定义对象有自己的operator()

[=]可能有点危险,因为它会默默地捕获所提到的所有内容,因此您可以按值[var1, var2, var2]列出要捕获的变量。

Concurrency::concurrent_queue< std::function< void() > > safe_queue;

// add to queue a call to `hello( some_string, some_int )`

void hello_to_queue( std::string some_string, int some_int ) {
  safe_queue.push( [=]{ hello(some_string, some_int); } );
}

some_stringsome_int复制到lambda中,将lambda移动到std::function<void()>,然后将std::function<void()>存储在队列中。

// dequeuer, runs elements until it cannot find any more:
void try_run() {
  std::function<void()> f;
  while(safe_queue.try_pop(f)) {
    f();
    f = nullptr;
  }
}

这反复尝试从队列中提取std::function<void()>。每次成功时,它都会运行提取的函数。然后它会在尝试再次填充std::function之前回收内存,因为为什么不提前做,而不是在并发代码中。

当然,一个真实的用例涉及信号等。

std::function<?>存储如何调用存储对象以及对象的任何存储。

lambda可以捕获你想要的任何值,并公开一些要调用的代码。

他们一起让你存储&#34;在以后的某个时间运行它不再有参数&#34;,将它存储在你的并发队列中,并在另一个线程中运行它。

std::function实现可以将SBO用于小型捕获数据案例以消除任何分配。 lambda捕获中的移动表达式可以防止复制任何拥有的缓冲区(如std::stringstd::vectorstd::unique_ptr)。在std函数的体面质量实现中,捕获的数据存储在类型擦除pimpl的旁边,因此它将符合您的手动实现的效率。由于它使用RAII来拥有数据,因此不存在泄漏的风险。