C++ C/C+中工作窃取队列的实现+;?

C++ C/C+中工作窃取队列的实现+;?,c++,multithreading,algorithm,queue,work-stealing,C++,Multithreading,Algorithm,Queue,Work Stealing,我正在寻找C/CPP中工作窃取队列的正确实现。我环顾了谷歌,但没有发现任何有用的东西 也许有人熟悉一个好的开源实现?(我不喜欢实现从原始学术论文中提取的伪代码)。我不认为使用工作窃取,但这是第一步。我不知道还有其他的开源库用于此目的。的类使用工作窃取队列来实现它。如果您需要一个用于线程的WSQ,我建议您这样做。 如果您实际上是在寻找源代码,我不知道代码是否在ppl.h中给出,或者是否存在预编译对象;今晚到家时我得检查一下。没有免费午餐。 请看一看。这篇论文很难理解。我知道这篇论文包含的是理论证明

我正在寻找C/CPP中工作窃取队列的正确实现。我环顾了谷歌,但没有发现任何有用的东西

也许有人熟悉一个好的开源实现?(我不喜欢实现从原始学术论文中提取的伪代码)。

我不认为使用工作窃取,但这是第一步。我不知道还有其他的开源库用于此目的。

的类使用工作窃取队列来实现它。如果您需要一个用于线程的WSQ,我建议您这样做。

如果您实际上是在寻找源代码,我不知道代码是否在ppl.h中给出,或者是否存在预编译对象;今晚到家时我得检查一下。

没有免费午餐。

请看一看。这篇论文很难理解。我知道这篇论文包含的是理论证明,而不是伪代码。然而,没有比TBB更简单的版本了。如果有的话,它不会提供最佳性能。盗用工作本身会带来一些开销,因此优化和技巧非常重要。特别是,出列必须是线程安全的。实现高可扩展性和低开销的同步是一项挑战


我真想知道你为什么需要它。我认为适当的实现意味着像TBB和Cilk这样的东西。同样,偷工作很难实现。

有一种工具可以简单地以非常优雅的方式实现。这是一种在很短的时间内实现程序并行化的非常有效的方法

HPC挑战奖

参加HPC挑战赛的Cilk项目 二等奖获得2006年度最佳学生奖 ``优雅与优雅的最佳结合 性能“”。该奖项是在 2006年11月14日在坦帕举行的SC'06

从理论上讲,实施“工作偷盗”并不难。您需要一组队列,其中包含通过计算和生成其他任务来完成更多工作的任务。您需要对队列进行原子访问,以便将新生成的任务放入这些队列中。最后,您需要在每个任务结束时调用一个过程,以便为执行该任务的线程找到更多的工作;该过程需要在工作队列中查找工作

大多数这样的工作窃取系统都假设存在少量线程(通常由实际处理器内核备份),并且每个线程只有一个工作队列。然后,您首先尝试从自己的队列中窃取工作,如果队列为空,则尝试从其他队列中窃取工作。棘手的是知道要查看哪些队列;连续扫描它们以查找工作非常昂贵,并且会在寻找工作的线程之间产生大量争用

迄今为止,这都是非常普通的东西,主要有12个例外:1)切换上下文(例如,设置处理器上下文寄存器,例如“堆栈”)不能用纯C或C++来说明。您可以通过同意用特定于目标平台的机器代码编写包的一部分来解决这个问题。2)对多处理器的队列的原子访问不能纯粹在C或C++中完成(忽略德克尔的算法),因此需要使用汇编语言同步原语(如x86锁xCH或比较和交换)对这些代码进行编码。现在,一旦你有了安全的访问权限,更新queuse所涉及的代码并不复杂,你可以用几行C语言轻松编写

但是,我想你会发现,用混合汇编程序在C和C++中编写这样一个包仍然是低效的,最终你会在汇编程序中对整个事情进行编码。剩下的就是C/C++兼容的入口点:-}

我这样做是为了我们的并行编程语言,它提供了任意数量的并行计算在任何时刻都是实时的和交互的(同步的)。它在X86上的后台实现,每个CPU只有一个线程,并且完全在汇编程序中实现。窃取工作的代码可能总共有1000行,这是一个棘手的代码,因为您希望它在非争用情况下非常快

< > C和C++中的真正的飞碟是,当你创建一个代表工作的任务时,你分配了多少堆栈空间?串行C/C++程序通过简单地过度分配一个线性堆栈的大量(例如10Mb)来避免这个问题,并且没有人关心浪费了多少堆栈空间。但是,如果您可以创建数千个任务,并让它们都在某一特定时刻运行,那么您就无法合理地为每个任务分配10Mb的内存。因此,现在您要么需要静态地确定任务需要多少堆栈空间(图灵硬),要么需要分配堆栈块(例如,每个函数调用),而广泛使用的C/C++编译器不会这样做(例如,您可能正在使用的)。最后一种解决方法是限制任务创建,将其限制在任何时刻的几百个,并在活动任务中复用几百个真正巨大的堆栈。如果任务可以互锁/挂起状态,则不能执行最后一个任务,因为您将运行到阈值。因此,只有当任务只执行计算时,才能执行此操作。这似乎是一个相当严重的限制


为PARLANSE,我们构建了一个编译器,它为每个函数调用分配堆上的激活记录。

< P>如果您在C++上构建了一个独立的工作窃取队列类实现,它是基于pToX或Booo::线程,祝你们好运,据我所知,没有一个。 然而,正如其他人所说的那样,Cilk、TBB和微软的PPL都在幕后实现了工作测试

问题是要使用工作测试队列还是实现工作测试队列?如果您只想使用其中一个,那么上面的选择是很好的起点,只需在其中任何一个中安排一个“任务”就足够了

正如BlueRaja所说,PPL中的任务组和结构化任务组
#pragma once
#include <atomic>

  // A lock-free stack.
  // Push = single producer
  // Pop = single consumer (same thread as push)
  // Steal = multiple consumer

  // All methods, including Push, may fail. Re-issue the request
  // if that occurs (spinwait).

  template<class T, size_t capacity = 131072>
  class WorkStealingStack {

  public:
    inline WorkStealingStack() {
      _top = 1;
      _bottom = 1;
    }

    WorkStealingStack(const WorkStealingStack&) = delete;

    inline ~WorkStealingStack()
    {

    }

    // Single producer
    inline bool Push(const T& item) {
      auto oldtop = _top.load(std::memory_order_relaxed);
      auto oldbottom = _bottom.load(std::memory_order_relaxed);
      auto numtasks = oldbottom - oldtop;

      if (
        oldbottom > oldtop && // size_t is unsigned, validate the result is positive
        numtasks >= capacity - 1) {
        // The caller can decide what to do, they will probably spinwait.
        return false;
      }

      _values[oldbottom % capacity].store(item, std::memory_order_relaxed);
      _bottom.fetch_add(1, std::memory_order_release);
      return true;
    }

    // Single consumer
    inline bool Pop(T& result) {

      size_t oldtop, oldbottom, newtop, newbottom, ot;

      oldbottom = _bottom.fetch_sub(1, std::memory_order_release);
      ot = oldtop = _top.load(std::memory_order_acquire);
      newtop = oldtop + 1;
      newbottom = oldbottom - 1;

      // Bottom has wrapped around.
      if (oldbottom < oldtop) {
        _bottom.store(oldtop, std::memory_order_relaxed);
        return false;
      }

      // The queue is empty.
      if (oldbottom == oldtop) {
        _bottom.fetch_add(1, std::memory_order_release);
        return false;
      }

      // Make sure that we are not contending for the item.
      if (newbottom == oldtop) {
        auto ret = _values[newbottom % capacity].load(std::memory_order_relaxed);
        if (!_top.compare_exchange_strong(oldtop, newtop, std::memory_order_acquire)) {
          _bottom.fetch_add(1, std::memory_order_release);
          return false;
        }
        else {
          result = ret;
          _bottom.store(newtop, std::memory_order_release);
          return true;
        }
      }

      // It's uncontended.
      result = _values[newbottom % capacity].load(std::memory_order_acquire);
      return true;
    }

    // Multiple consumer.
    inline bool Steal(T& result) {
      size_t oldtop, newtop, oldbottom;

      oldtop = _top.load(std::memory_order_acquire);
      oldbottom = _bottom.load(std::memory_order_relaxed);
      newtop = oldtop + 1;

      if (oldbottom <= oldtop)
        return false;

      // Make sure that we are not contending for the item.
      if (!_top.compare_exchange_strong(oldtop, newtop, std::memory_order_acquire)) {
        return false;
      }

      result = _values[oldtop % capacity].load(std::memory_order_relaxed);
      return true;
    }

  private:

    // Circular array
    std::atomic<T> _values[capacity];
    std::atomic<size_t> _top; // queue
    std::atomic<size_t> _bottom; // stack
  };