我有一个非常古老,非常大,完全正常的C程序,它可以玩棋盘游戏。我想转换它(或者我应该说它的一部分)在多个线程中工作,这样我就可以利用多核处理器。在旧程序中有一个名为board []的全局UBYTE数组。有许多(高度优化,高速关键)函数操纵board []的内容。我现在想让这个过程如下工作:
步骤1.执行大量的 单线程中的操作 执行许多操作 单板[]。这些东西太复杂,无法在多个核心上执行。
步骤2.退出农场 多个“board []”副本到a 线程的集合,每个 线程花一些时间做他们的 对他们的独立操纵 拥有私人“董事会[]”。
第3步。 线程完成了他们的工作 返回主要答案 线程。
为了论证,可以说会有32个子线程。
现在,一种方法是制作一个全局board []和32个子板,使用不同的名称,如sub_board [32] [],然后编写一组新的板操作函数,用于新的2维度sub_board [] [],但这会破坏我的优化,因为需要额外的乘法并为每次访问游戏板添加。此外,新版本的旧主板操作功能也会略显混乱。
现在我还没有成为C ++程序员(但我正在尽可能快地学习)并且有人提出了以下涉及C ++的技巧(我不确定我的所有细节都是正确的):我按原样离开现有的主板[]。我保留所有现有的板操作功能。我创建了一个新类(让我们称之为thread_type),它包含一个board []和一组新的板操作函数。像这样:
class thread_type
{
UBYTE board[]; // boards for slave threads to work with
void board_manipulation_A(void);
void board_manipulation_B(void);
}
除了在开头用“thread_type ::”声明之外,板操作函数与旧的函数相同(因此我可以剪切和粘贴)。然后在main()中我有:
class thread_type slave[32];
现在,我可以使用基本线程中的所有旧代码操作单个全局board []。然后我可以将主板[]复制到slave [n] .board []然后有
For (i = 0; i < 32;i++)
{
// there will have to be some extra thread/mutex
// related code around here but I'm not showing it for simplicity
slave[n].do_your_stuff();
}
现在在32个线程中的每个线程中,每个线程将使用自己不同的“board []”,其代码与旧的原始(完全调试和优化)代码几乎完全相同。我甚至可以通过做一些#define技巧来完全避免旧代码的剪切和过去,即使用这样的函数声明
void THREAD_OR_BASE board_manipulation_A(void);
然后用
运行一次#define THREAD_OR_BASE // zilch
和
一次#define THREAD_OR_BASE thread_type::
这样我可以非常肯定,每当我对board_manipulation_A()进行修改时,它都会出现在基本线程版本和子线程版本中。
我的问题是:A)这一切都有效吗? B)我是否错过了一些重要的步骤? C)我可以通过一些更简单的方法实现同样的目标吗?
编辑:而不是32个线程,我应该说“与核心一样多的线程”
答案 0 :(得分:2)
如果你不需要将线程结果板合并回同一个,看起来像一个好的策略,让每个线程都有自己的板副本并且正在处理它,我不明白为什么它不应该工作。
然而,在我看来,线程会执行很多cpu绑定操作,如果这是真的,你不应该有那么多线程,最好拥有与你的cpu所拥有的核心相同的线程,或者更多,如果更多,他们将争夺cpu资源,你的表现将会降低。
答案 1 :(得分:1)
外国代码通常有一堆全局状态和设置(在你的情况下也可能在board []旁边)。如果线程通过方法使用这些变量,那么这些变量也必须打包到您的类中(除非它们是只读的)。检查函数内部的静态变量(本地静态实际上也是全局的,因为它们对应用程序只存在一次)它们也不是线程安全的。
我猜你已经做过这些检查了。我希望这个答案能帮助其他想要对现有应用程序进行类似修改的人......
答案 2 :(得分:1)
完成一个解释C ++继承的教程。它不是用宏完成的。
这是一种设计气味,你必须有一个以上的函数定义,甚至更多的是你必须根据谁调用函数(主线程的过程或从属线程的过程)有多个定义。
不是创建类来描述线程的行为,而是创建一个Board
类,它具有两个操作和板的数据,以及任何全局变量。无论是在一个线程还是另一个线程中调用它都无关紧要。如果您的董事会成员有UBYTE board[]
成员,则您不必更改现有代码,也不会滥用该语言。
如果您希望并行工作包执行不同的操作,请在该工作程序的过程中调用不同的函数。使用工作者的行为打包一个板是个好主意,但让工作者在板对象上调用函数而不是直接使用低级表示。
假设你的处理器少于32个,你完全有可能不会有太大的改进 - 如果操作速度很快,你会花费同样多的时间创建操作系统线程并在它们之间进行切换。做任何有用的事。如果你感觉很勇敢,那么看看你创建的核心数量和你拥有的CPU核心一样多,以及32个工作包的队列,这样每个线程都会从队列中拉出下一个包裹,运行它,然后存储结果。这样你就不会为小工作项创建线程。我倾向于自己动手,但粗略地看一下这个threadpool似乎很容易用来抽象“我希望并行运行的32个任务”和“允许我运行任务的操作系统功能”我机器上的不同内核'。
OO设计的主要规则之一是用它操作的数据封装行为。
因此,表示与电路板相关的数据和操作的类可能如下所示:
class Board
{
private:
UBYTE board[ BOARD_SIZE ];
public
// ... suitable constructors
void manipulation_A ();
void manipulation_B ();
};
您更改为现有代码(假设board
是唯一的全局数据),以使Board
类的函数成员函数:
void Board::manipulation_A ()
{
...
}
void Board::manipulation_B ()
{
...
}
您的工作对象每个都有自己的工作板:
class WorkItemAAB
{
private:
Board board;
public:
void run () {
board.manipulation_A();
board.manipulation_A();
board.manipulation_B();
}
};
或你想要的任何操作顺序。
您可以从任何地方调用board对象的函数,它只影响属于该对象的数据。没有必要使用宏在本地和全局板之间切换,或者编译相同的代码两次。
您的主线程可以进行操作,然后创建工作项,每个工作项都有自己的板数据副本,并将它们推送到单独的线程/使用更高级别的并发调度程序排队。