并行命令模式

Parallel Command Pattern

我想知道如何在保持性能的同时保证线程安全地使用命令模式。我有一个模拟,其中我执行了 数百亿 次以上的迭代;性能至关重要。

在这个模拟中,我有一堆 Moves 对我模拟中的对象执行命令。基础 class 看起来像这样:

class Move
    {
        public:
            virtual ~Move(){}

            // Perform a move.
            virtual void Perform(Object& obj) = 0;

            // Undo a move.
            virtual void Undo() = 0;
    };

我在 Perform 上而不是在构造函数中传递对象的原因,就像典型的命令模式一样,是因为我无法在每次迭代中实例化一个新的 Move 对象。相反,Move 的具体实现将简单地采用 Object,维护指向它的指针以及它在需要时的先前状态。下面是一个具体实现的例子:

class ConcreteMove : public Move
    {
        std::string _ns;
        std::string _prev;
        Object* _obj;
        ConcreteMove(std::string newstring): _ns(newstring) {}            


        virtual void Perform(Object& obj) override
        {
            _obj= &obj;
            _prev = obj.GetIdentifier();
            obj.SetIdentifier(_ns);
        }

        virtual void Undo()
        {
            _obj->SetIdentifier(_prev);
        }
    };

不幸的是,这让我付出了线程安全的代价。我想并行化我的循环,其中多个迭代器同时对一堆对象执行移动。但显然 ConcreteMove 的一个实例由于我的实现方式而无法重复使用。

我考虑过 Perform return 一个可以传递给 UndoState 对象,这样可以使实现线程安全,因为它独立于ConcereteMove 状态。但是,在每次迭代中创建和销毁此类对象的成本太高。

此外,模拟具有 Moves 向量,因为每次迭代都可以执行多个移动,存储在 MoveManager class 中,其中包含 Move 对象的向量由客户端实例化的指针。我这样设置是因为每个特定具体移动的构造函数都采用参数(见上例)。

我考虑过为 MoveMoveManager 编写一个复制运算符,以便它可以在线程之间复制,但我不认为这是一个正确的答案,因为那样的话所有权Move 个对象落在 MoveManager 而不是客户端(只对第一个实例负责)。此外,对于 MoveManager 和维护它的责任也是如此。

更新:这是我的 MoveManager 如果重要的话

class MoveManager
{
    private:

        std::vector<Move*> _moves;

    public:

        void PushMove(Move& move)
        {
            _moves.push_back(&move);
        }


        void PopMove()
        {
            _moves.pop_back();
        }

        // Select a move by index.
        Move* SelectMove(int i)
        {
            return _moves[i];
        }

        // Get the number of moves.
        int GetMoveCount()
        {
            return (int)_moves.size();
        }
};

说明:我只需要每个线程一组 Move 个对象。每次迭代都会重复使用它们,其中每次在不同的对象上调用 Perform

有谁知道如何以线程安全的方式有效地解决这个问题?

谢谢!

不可能满足规定的要求。具体来说,

  1. 使用命令模式。 "the command pattern is a behavioral design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time." 因此您正在存储数据。
  2. 你"can't afford"分配内存。
  3. 您有 "billions" 次迭代,这意味着一些大型静态分配是不够的。

您想存储数据而没有任何地方可以存储它。因此没有答案。但是,如果你愿意改变你的需求,无疑有很多方法可以解决你的问题(不管是什么——我从描述中看不出来。)

我也无法估计您一次需要多少个 Move 对象。如果这个数字相当低,那么专门的分配方案可能会解决您的部分问题。同样,如果大多数 Move 对象都是重复的,则不同的专用分配方案可能会有所帮助。

一般你问的是解决不了的,放宽要求应该不难。

关于线程 ID 的概念呢?另外,为什么不预先构造标识符字符串并将指针传递给它们?

class ConcreteMove : public Move
{
  std::string *_ns;
  std::vector<std::string *> _prev;
  std::vector<Object *> _obj;

  ConcreteMove(unsigned numthreads, std::string *newstring)
    : _ns(newstring),
      _prev(numthreads),
      _obj(numthreads)
  {
  }

  virtual void Perform(unsigned threadid, Object &obj) override
  {
    _obj[threadid] = &obj;
    _prev[threadid] = obj.GetIdentifier();
    obj.SetIdentifier(_ns);
  }

  virtual void Undo(unsigned threadid)
  {
    _obj[threadid]->SetIdentifier(_prev[threadid]);
  }
};

您的移动管理器不应包含指针向量,它应该是移动对象向量

std::vector<Move> _moves;

似乎每个线程都有一个移动管理器,所以没有多线程问题,将向量容量设置为最大,然后在向量中的移动上应用执行和其他操作 没有新的分配,您将重复使用移动对象