3

そこで、初めてマルチスレッド プログラミングを試してみたところ、このヒープ破損の問題に遭遇しました。基本的に、プログラムはランダムな時間 (最短 2 秒、最長 200 秒) 実行された後、クラッシュしてヒープ破損エラーを吐き出します。この件について私が読んだことはすべて、エラーを引き起こす原因と実際にエラーを引き起こす原因とはほとんど関係がないため、診断が非常に難しいことを示しています。そういうわけで、私は困惑したままです。

ただし、私はマルチスレッドについて正式に教えられたわけではないので、ほとんどの場合、概念について理解したことに基づいてプログラミングしていました。私のコードは完全に間違っている可能性があります。だから、ここに私がやろうとしていることと、プログラムが現在それを処理しようとしている方法の基本的な要約があります:

背景のいくつかの視差レイヤーを描画する単純なゲームのコードを書いています。これらのレベルは非常に大きい (たとえば 20000x5000 ピクセル) ため、これらのサイズの画像の 3 つのレイヤーを読み込もうとすることは (不可能ではないにしても) 明らかに不可能です。したがって、現在、画像は500x500の画像に分割されており、すぐに表示する必要がある画像のみをメモリに保持するコードがあります。読み込まれた不要な画像はメモリから削除されます。ただし、単一のスレッドでは、これにより、続行する前にイメージがロードされるのを待っている間に、プログラムが大幅にハングアップします。

これが、私にとってマルチスレッドが理にかなっていると思われるところです。実際に必要な時間までに画像が読み込まれている限り、ゲームの滑らかさに影響を与えることなく、プログラムが必要な読み込みを行うようにしたかったのです。だからここに私がそれを整理した方法があります:

1.) 画像の移動先に関するすべてのデータとそれらに関連付けられたデータはすべて 1 つの多次元配列に格納されますが、最初は画像データは読み込まれません。各フレームで、コードは配列の各位置をチェックし、画像を配置する場所がプレーヤーの半径内にあるかどうかをテストします。

2.) そうである場合、このスポットにロードが必要であるというフラグが立てられます。画像をロードする場所へのポインターは、ベクターに push_back() されます。

3.) レベルが開始されると、2 番目のスレッドが開始されます。このスレッドには、最初に前述のベクトルへのポインターが渡されます。

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 ファイル内には、格納されているすべての levelArt データを反復処理するための 3 つの入れ子になった for ループが存在し、それらが表示されるプレーヤーの近くに存在するかどうかをテストします。

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

これは、マルチスレッドの合理的な使用方法のようです。重要な考え方 (つまり、間違った方法で問題が発生する主な場所) は、複数のスレッドで使用されるデータに注意する必要があるということです。

そのようなデータがある場所は 2 つあります。

  1. ベクトル (ちなみに、これはおそらくキューである必要があります)
  2. データを返す配列

物事を整理する 1 つの方法 (決して唯一の方法ではありません) は、これらのそれぞれを独自のクラス (たとえば、ベクトルのメンバー変数を持つクラス) にラップすることです。クラスのメソッドを介してのみ、ベクトルへの直接アクセスを許可しないでください。次に、たとえばミューテックスまたは適切な同期オブジェクトを使用して、メソッドを同期します。個々のメソッドだけでなく、オブジェクトへのアクセスを同期していることに注意してください。そのため、「キューから読み取る」メソッドにミューテックスを配置するだけでは不十分です。「キューからの読み取り」メソッドと「キューへの書き込み」メソッドに共通のミューテックスが必要です。これにより、一方が発生している間に一方が実行されることはありません。(また、mutex という用語を使用していることにも注意してください。プラットフォームや正確な状況によっては、これを使用するのは非常に間違っている可能性があります。

同期により、プログラムはスレッドセーフになります。それは、プログラムを効率的にすることとは異なります。これを行うには、おそらく、キュー内のアイテムの数を表すセマフォが必要であり、while ループを実行するのではなく、そのセマフォで「データ ロード スレッド」を待機させます。

于 2012-08-05T04:49:45.537 に答える