我想知道如何在保持性能的同时使用命令模式线程安全。我有一个仿真,其中执行了数百亿次迭代。性能至关重要。
在此模拟中,我有一堆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
的 vector ,这是因为存储在MoveManager
类中的每个迭代都可以执行多次移动,该类包含由客户端实例化的Move
对象指针的 vector 。我之所以这样设置,是因为每个具体Concrete Action 的构造函数都带有参数(请参见上面的示例)。我考虑过为
Move
和MoveManager
写一个复制运算符,以便可以在线程之间复制它,但是我不认为这是一个正确的答案,因为那时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
。 有谁知道如何以线程安全的方式有效解决此问题?
谢谢!
最佳答案
线程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]);
}
};