所以我刚开始尝试一些多线程编程,而且我遇到了这个堆损坏问题。基本上,程序将在崩溃和吐出堆损坏错误之前运行一段随机的时间长度(短至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循环迭代器。
答案 0 :(得分:1)
这似乎是合理使用多线程。关键的想法(换句话说,如果你做错了,你会遇到问题的主要地方)是你必须要小心多个线程使用的数据。
您有两个地方有这样的数据:
安排事物的一种方法 - 绝不是唯一的方法 - 将每个方法包装到它自己的类中(例如,具有向量的成员变量的类)。不允许任何直接访问向量,只能通过类中的方法。然后同步方法,例如使用互斥锁或任何适当的同步对象。请注意,您正在同步对象的访问权限,而不仅仅是单个方法。因此,在“从队列中读取”方法中放置互斥量是不够的;你需要在“从队列中读取”和“写入队列”方法中使用一个共同的互斥锁,以便在另一个发生时没有人在做一个。 (另请注意,我使用的是mutex术语;根据您的平台和具体情况,这可能是一个非常错误的用法。我可能会在Windows上使用信号量和关键部分。)
同步将使程序成为线程安全的。这与使程序高效不同。为此,您可能需要一个表示队列中项目数的信号量,并让“加载数据线程”等待该信号量,而不是执行while循环。