转换旧C代码以使用线程

时间:2009-09-17 10:47:29

标签: c++ multithreading

我有一个非常古老,非常大,完全正常的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个线程,我应该说“与核心一样多的线程”

3 个答案:

答案 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对象的函数,它只影响属于该对象的数据。没有必要使用宏在本地和全局板之间切换,或者编译相同的代码两次。

您的主线程可以进行操作,然后创建工作项,每个工作项都有自己的板数据副本,并将它们推送到单独的线程/使用更高级别的并发调度程序排队。