并行命令模式

时间:2015-03-14 22:59:16

标签: c++ multithreading oop

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

在这个模拟中,我有一堆Moves在我的模拟中对对象执行命令。基类如下所示:

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

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

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

我有Perform而不是构造函数传递对象的原因,就像Command模式一样,我不能在每次迭代时实例化一个新的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返回一个State对象,该对象可以传递给Undo,这样可以使实现成为线程安全的,因为它独立于ConcereteMove州。但是,在每次迭代中创建和销毁此类对象的成本太高。

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

我考虑为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

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

谢谢!

3 个答案:

答案 0 :(得分:3)

线程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]);
  }
};

答案 1 :(得分:1)

不符合规定的要求。具体地,

  1. 使用命令模式。 &#34;命令模式是一种行为设计模式,其中一个对象用于表示和封装稍后调用方法所需的所有信息。&#34;因此,您将存储数据。
  2. 你&#34;买不起&#34;分配内存。
  3. 你有&#34;数十亿&#34;迭代,这意味着一些大的静态分配不够。
  4. 您希望存储数据而无需存储任何地方。因此没有答案。但是,如果您愿意改变您的要求,毫无疑问有很多方法可以解决您的问题(不管它是什么 - 我无法从描述中说出来。)

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

    一般来说,你所提出的要求无法解决,但放宽要求并且不应该很难。

答案 2 :(得分:1)

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

std::vector<Move> _moves;

似乎每个线程都有一个Move Manager,因此不存在多线程问题,将向量容量设置为max,然后在向量中移动时应用执行和其他操作 没有新的分配,您将重用移动对象