并行命令模式
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 一个可以传递给 Undo
的 State
对象,这样可以使实现线程安全,因为它独立于ConcereteMove
状态。但是,在每次迭代中创建和销毁此类对象的成本太高。
此外,模拟具有 Moves
向量,因为每次迭代都可以执行多个移动,存储在 MoveManager
class 中,其中包含 Move
对象的向量由客户端实例化的指针。我这样设置是因为每个特定具体移动的构造函数都采用参数(见上例)。
我考虑过为 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
。
有谁知道如何以线程安全的方式有效地解决这个问题?
谢谢!
不可能满足规定的要求。具体来说,
- 使用命令模式。 "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." 因此您正在存储数据。
- 你"can't afford"分配内存。
- 您有 "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;
似乎每个线程都有一个移动管理器,所以没有多线程问题,将向量容量设置为最大,然后在向量中的移动上应用执行和其他操作
没有新的分配,您将重复使用移动对象
我想知道如何在保持性能的同时保证线程安全地使用命令模式。我有一个模拟,其中我执行了 数百亿 次以上的迭代;性能至关重要。
在这个模拟中,我有一堆 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 一个可以传递给 Undo
的 State
对象,这样可以使实现线程安全,因为它独立于ConcereteMove
状态。但是,在每次迭代中创建和销毁此类对象的成本太高。
此外,模拟具有 Moves
向量,因为每次迭代都可以执行多个移动,存储在 MoveManager
class 中,其中包含 Move
对象的向量由客户端实例化的指针。我这样设置是因为每个特定具体移动的构造函数都采用参数(见上例)。
我考虑过为 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
。
有谁知道如何以线程安全的方式有效地解决这个问题?
谢谢!
不可能满足规定的要求。具体来说,
- 使用命令模式。 "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." 因此您正在存储数据。
- 你"can't afford"分配内存。
- 您有 "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;
似乎每个线程都有一个移动管理器,所以没有多线程问题,将向量容量设置为最大,然后在向量中的移动上应用执行和其他操作 没有新的分配,您将重复使用移动对象