3

所以我刚开始第一次尝试一些多线程编程,我遇到了这个堆损坏问题。基本上,程序会运行一段随机的时间(短至 2 秒,长至 200 秒),然后崩溃并抛出堆损坏错误。我读到的关于这个主题的所有内容都表明它很难诊断,因为触发错误的原因通常与实际导致它的原因无关。因此,我仍然很难过。

然而,我还没有被正式教授多线程,所以我主要是根据我对这个概念的理解进行编程,我的代码可能完全错误。所以这是我正在尝试做的事情以及程序当前如何处理它的基本概要:

我正在为一个简单的游戏编写代码,该游戏涉及绘制几个视差层的背景。这些级别非常大(例如 20000x5000 像素),因此显然尝试加载 3 层这些大小的图像是不可行的(如果不是不可能的话)。所以目前图像被分割成 500x500 的图像,我的代码只有它需要立即显示的图像保存在内存中。它已加载的任何不再需要的图像都会从内存中删除。但是,在单个线程中,这会导致程序在等待图像加载后再继续时显着挂起。

这就是多线程对我来说合乎逻辑的地方。我希望程序完成它需要做的加载,而不影响游戏的流畅性,只要图像在实际需要的时候加载。所以这是我的组织方式:

1.) 图像应该去哪里的所有数据以及与它们关联的任何数据都存储在一个多维数组中,但最初没有加载图像数据。每一帧,代码都会检查数组上的每个位置,并测试图像应该去的位置是否在玩家的某个半径范围内。

2.) 如果是,则将此位置标记为需要加载。指向应该加载图像的位置的指针是 push_back()'d 到向量上。

3.) 第二个线程在关卡开始后启动。该线程最初被传递一个指向上述向量的指针。

4.) 这个线程被放入一个无限的 While 循环(这本身听起来是错误的),只有在线程终止时才会终止。这个循环不断检查向量中是否有任何元素。如果有,它会抓取第 0 个元素,将图像数据加载到该指针中,然后 .erase() 是向量中的元素。

这几乎是它如何工作的概要。我没有受过教育的假设是 2 个线程在某个时候发生冲突,试图一次在同一个空间中写入和删除或其他东西。鉴于我是新手,我敢肯定这种方法在某种程度上是可怕的,所以我很想听听我应该改进什么。

编辑:根据要求添加源代码:

class ImageLoadQueue
{
private:
ImageHandle* image;
std::string path;
int frameWidth, frameHeight, numOfFrames;
public:
ImageLoadQueue();
ImageLoadQueue(ImageHandle* a, std::string b, int c, int d, int e=1) { setData(a,b,c,d,e); }

void setData(ImageHandle* a, std::string b, int c, int d, int e=1)
{
    image = a;
    path = b;
    frameWidth = c;
    frameHeight = d;
    numOfFrames = e;
}
void loadThisImage() { image->loadImage(path, frameWidth, frameHeight, numOfFrames, numOfFrames); }
};

class ImageLoadThread : public sf::Thread
{
private:
std::vector<ImageLoadQueue*>* images;

public:
ImageLoadThread() { };
ImageLoadThread(std::vector<ImageLoadQueue*>* a) { linkVector(a); }

void linkVector(std::vector<ImageLoadQueue*>* a) { images = a; }
virtual void Run()
{
    while (1==1)
    {

        if (!images->empty())
        {
            (*images)[0]->loadThisImage();
            images->erase(images->begin());     
        }
    }
}

};


class LevelArt
{
private:
int levelWidth, levelHeight, startX, startY, numOfLayers;
float widthScale, heightScale, widthOfSegs, heightOfSegs;
float* parallaxFactor;
ImageHandle** levelImages;
int** frame;
int** numOfFrames;
bool* tileLayer;
bool** isLoaded;
Animation** animData;
std::string** imagePath;

std::vector<ImageLoadQueue*> imageQueue;
ImageLoadThread imageThread;

 public:
LevelArt(void);
LevelArt(std::string);
~LevelArt(void);

void loadData(std::string);
void drawLevel(sf::RenderWindow*, float, float);
void scaleLevel(float, float);
void forceDraw(sf::RenderWindow*);
void wipeLevel();
void initialLoad();

int getLevelWidth() { return levelWidth; }
int getLevelHeight() { return levelHeight; }
int getTotalWidth() { return widthOfSegs*levelWidth; }
int getTotalHeight() { return heightOfSegs*levelHeight; }
int getStartX() { return startX; }
int getStartY() { return startY; }
};

这是此标头中的大部分相关线程代码。在 levelArt.cpp 文件中存在 3 个嵌套的 for 循环,用于遍历所有存储的 levelArt 数据,测试它们是否离玩家足够近以便显示,其中它调用:

imageQueue.push_back(new ImageLoadQueue(&levelImages[i][(j*levelWidth)+k], imagePath[i][(j*levelWidth)+k], widthOfSegs, heightOfSegs, numOfFrames[i][(j*levelWidth)+k]));

i,j,k 是 for 循环迭代器。

4

1 回答 1

1

这似乎是对多线程的合理使用。关键思想(换句话说,如果你做错了你会遇到问题的主要地方)是你必须小心被多个线程使用的数据。

您有两个地方拥有此类数据:

  1. 向量(顺便说一下,它可能应该是一个队列)
  2. 返回数据的数组

安排事物的一种方法——绝不是唯一的——是将这些中的每一个包装到它自己的类中(例如,具有向量成员变量的类)。不允许任何直接访问向量,只能通过类上的方法。然后同步方法,例如使用互斥锁或任何适当的同步对象。请注意,您正在同步对对象的访问,而不仅仅是单个方法。所以在“从队列中读取”方法中放置一个互斥锁是不够的;您需要在“从队列读取”和“写入队列”方法中使用一个通用互斥锁,这样当另一个发生时没有人在做一个。(另请注意,我使用的是互斥锁这个术语;根据您的平台和具体情况,这可能是一个非常错误的使用方法。

同步将使程序线程安全。这与使程序高效不同。为此,您可能需要一个表示队列中项目数的信号量,并让您的“加载数据线程”等待该信号量,而不是执行 while 循环。

于 2012-08-05T04:49:45.537 回答