2012-08-05 101 views
2

所以我剛開始嘗試一些多線程編程的第一次,我遇到了這個堆腐敗問題。基本上,程序會在崩潰並吐出堆損壞錯誤之前運行一段隨機時間(短至2秒,長達200)。我讀過的關於這個主題的所有內容都說明了它很難診斷,因爲引發錯誤的原因通常與實際造成它的原因無關。因此,我仍然難倒。多線程和堆損壞

我還沒有正式教多線程不過,所以我主要是編程關閉我的理解這個概念的,而我的代碼可能是完全錯誤的。因此,這裏有我想要做的,是如何計劃目前正努力處理它一個基本的破敗:

我在寫代碼的簡單遊戲,涉及繪畫背景的幾個parallaxing層。這些級別非常大(例如20000x5000像素),因此顯然嘗試加載3層這些大小的圖像是不可行的(如果不是不可能的話)。所以目前圖像被分割成500x500圖像,而且我的代碼只有它需要顯示的圖像才能保存在內存中。它已加載的任何圖像不再需要從內存中刪除。但是,在單個線程中,這會導致程序在等待圖像加載之前顯着掛起,然後再繼續。

這是多線程對我來說似乎合乎邏輯的地方。我希望程序在不影響遊戲平滑度的情況下進行加載操作,只要圖像在實際需要時加載即可。所以這裏是我如何組織它:

1.)圖像應該去的地方的所有數據和與它們相關的任何數據都存儲在一個多維數組中,但最初沒有加載圖像數據。每一幀,代碼檢查陣列上的每個位置,並測試圖像應該在哪個點位於播放器的某個半徑範圍內。

2。)如果是,它標誌這點爲需要加載。一個指向圖像應該加載到的位置的指針是push_back()到一個向量。

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

這是非常難以診斷的一個問題沒有源代碼的程序。 – 2012-08-05 04:16:27

+0

那麼我可以發佈源代碼,如果我需要,但我想知道我在嘗試概念明智的是否堅實。我會嘗試挖掘代碼的相關部分,但很快就會發布。 – 2012-08-05 04:32:57

回答

1

這看起來像是多線程的合理使用。關鍵的想法(換句話說,如果你做錯了,你會遇到問題的主要地方)是你必須小心多個線程使用的數據。

你有,你有這樣的數據的兩個地方:

  1. 的載體(其中,順便說一句,也許應該是一個隊列)
  2. 的數組,其中返回的數據

一種方式安排事 - 絕不是隻有一個 - 將每個這些包裝成它自己的類(例如,其具有載體的成員變量的類)。不允許直接訪問矢量,只能通過課堂上的方法。然後同步這些方法,例如使用互斥鎖或適當的同步對象。請注意,您正在同步對象的訪問權限,而不僅僅是單個方法。因此,在「從隊列中讀取」方法中插入互斥體是不夠的;你需要在「從隊列讀取」和「寫入隊列」方法中使用一個共同的互斥體,以便沒有人在做另一個互相發生的事情。 (也請注意,我使用的術語互斥體;這可能是一個非常錯誤的東西,取決於您的平臺和確切的情況,我可能會在Windows上使用信號量和關鍵部分。)

同步將使該程序是線程安全的。這與使程序高效不同。爲此,您可能需要一個表示隊列中項目數量的信號量,並讓您的「加載數據線程」等待該信號量,而不是執行一個while循環。

+0

謝謝!不完全熟悉互斥或信號量,所以看起來像我有一些作業要做。 – 2012-08-05 04:59:57

+0

剛發現我一直使用的SFML完全包含了互斥體,而且在出於某種原因之前我必須跳過它,難怪我遇到了問題。這似乎很容易實現,所以我現在可能很好。謝謝你指點我正確的方向! – 2012-08-05 05:17:33

+0

@ChrisDilley - yup - 讀取互斥量,信號量,事件,criticalSection的內容。鎖,監視器,condvars等 - 掌握這些原語對於安全和有效的線程間通信是絕對必要的。使用criticalSection和信號管理生產者 - 消費者隊列的效率遠高於投票 - 正如Michael所指出的+1 – 2012-08-05 06:27:19