MFC 多线程与 delete[] , d​​bgheap.c

Posted

技术标签:

【中文标题】MFC 多线程与 delete[] , d​​bgheap.c【英文标题】:MFC multithreading with delete[] , dbgheap.c 【发布时间】:2009-11-17 02:03:49 【问题描述】:

我有一个奇怪的问题,真的不明白发生了什么。

我使用 MFC 多线程类使我的应用程序成为多线程的。

到目前为止一切正常,但现在:

我在代码开头的某个地方创建了线程:

            m_bucketCreator = new BucketCreator(128,128,32);
    CEvent* updateEvent = new CEvent(FALSE, FALSE);
    CWinThread** threads = new CWinThread*[numThreads];
    for(int i=0; i<8; i++)
        threads[i]=AfxBeginThread(&MyClass::threadfunction, updateEvent);
        m_activeRenderThreads++;
    

这会创建 8 个线程来处理这个函数:

UINT MyClass::threadfunction( LPVOID params ) //executed in new Thread


Bucket* bucket=m_bucketCreator.getNextBucket();

    ...do something with bucket...

delete bucket;


m_bucketCreator 是静态成员。现在,在尝试删除缓冲区时,我在 Bucket 的解构器中遇到了一些线程错误(但是,我理解它的方式,这个缓冲区应该在这个线程的内存中,所以我不明白为什么会出现错误)。在delete[] buffer 的尝试中,错误发生在_CrtIsValidHeapPointer() 中的dbgheap.c

Visual Studio 输出它捕获了一个停止点的消息,这可能是由于堆损坏或因为用户按下了 f12(我没有;))

class BucketCreator 
public:
    BucketCreator();

~BucketCreator(void);

void init(int resX, int resY, int bucketSize);

Bucket* getNextBucket()

Bucket* bucket=NULL;
//enter critical section
CSingleLock singleLock(&m_criticalSection);
singleLock.Lock();

int height = min(m_resolutionY-m_nextY,m_bucketSize);
int width = min(m_resolutionX-m_nextX,m_bucketSize);

bucket = new Bucket(width, height);

//leave critical section
singleLock.Unlock();
return bucket;


private:

int m_resolutionX;
int m_resolutionY;
int m_bucketSize;

int m_nextX;
int m_nextY;

//multithreading:
CCriticalSection m_criticalSection;
;

和类 Bucket:

class Bucket : public CObject
DECLARE_DYNAMIC(RenderBucket)
public:

Bucket(int a_resX, int a_resY)

resX = a_resX;
resY = a_resY;
buffer = new float[3 * resX * resY];

int buffersize = 3*resX * resY; 
for (int i=0; i<buffersize; i++)
    buffer[i] = 0;



~Bucket(void)
delete[] buffer;
buffer=NULL;



int getResX()return resX;
int getResY()return resY;
float* getBuffer()return buffer;

private:
int resX;
int resY;
float* buffer;

Bucket& operator = (const Bucket& other)  /*..*/
Bucket(const Bucket& other) /*..*/
;

谁能告诉我这可能是什么问题?

edit:这是我从线程调用的另一个静态函数。这样做安全吗?

static std::vector<Vector3> generate_poisson(double width, double height, double min_dist, int k, std::vector<std::vector<Vector3> > existingPoints)

    CSingleLock singleLock(&m_criticalSection);
    singleLock.Lock();

    std::vector<Vector3> samplePoints = std::vector<Vector3>();

            ...fill the vector...

            singleLock.Unlock();
            return samplePoints;
     

【问题讨论】:

你为什么要锁定你的关键部分来分配一个新的桶对象? getNextBucket 是如何被另一个线程重新输入的?你想同步访问什么? 我在顶部添加了创建线程的代码。 m_bucketCreator 是一个静态成员变量,被多个线程访问 【参考方案1】:

之前的所有回复都是正确的。对于复制构造函数,请确保它不只是复制缓冲区指针,否则会导致问题。它需要分配一个新缓冲区,而不是指针值,这会导致“删除”错误。但我不认为复制构造函数会在您的代码中被调用。

我查看了代码,但没有看到任何错误。请注意,在这个 GetNextBucket 代码中甚至不需要线程同步,因为它返回一个局部变量并且这些是线程前的。

发生 ValidateHeapPointer 错误是因为堆损坏了,当指针写入内存块时会发生这种情况。通常是 for() 循环走得太远,缓冲区分配得不够大,等等。

在调用“delete”期间会报告该错误,因为此时会在调试模式下验证堆是否存在错误。但是,该错误发生在此之前,只是碰巧只在“新建”和“删除”中检查了堆。此外,它不一定与 'Bucket' 类有关。

如果没有使用 BoundsChecker 或 HeapValidator 之类的工具,您需要找到此错误,只需将代码的部分注释掉,直到它消失,然后您就会找到有问题的代码。

还有另一种方法可以缩小问题范围。在调试模式下,包含在您的代码中,并在不同的兴趣点上调用 _CrtCheckMemory()。当堆损坏时,这将产生错误。只需移动代码中的调用,以缩小损坏开始发生的时间点。

我不知道您使用的是哪个版本的 Visual C++。如果您使用的是早期版本,例如 VC++ 6.0,请确保您在编译器选项中使用的是 C 运行时库的 Multitreaded DLL 版本。

【讨论】:

嘿!非常感谢你! _CrtCheckMemory() 帮助我找到了堆损坏的位置。我确实在计算其中一个循环中的索引时遇到了问题! :)【参考方案2】:

您正在构建一个 RenderBucket。您确定要从那里调用“Bucket”类的构造函数吗?它应该是这样的:

class RenderBucket : public Bucket 
  RenderBucket( int a_resX, int a_resY )
    : Bucket( a_resX, a_resY )
  
  

在 Bucket 类中使用初始化器将缓冲区设置为 NULL 是一个好主意……此外,将默认构造函数和复制构造函数设为私有将有助于确保不会使用它们。请记住.. 如果您不这样做,编译器会自动创建这些:

Bucket();  <-- default constructor
Bucket( int a_resx = 0, int a_resy = 0 )  <-- Another way to make your default constructor
Bucket(const class Bucket &B)  <-- copy constructor

【讨论】:

该死 - 再次出现同样的复制粘贴错误 - 抱歉 :( 它应该读取新的 Bucket 如果你简化问题,它可能会消失......你对桶做某事的“......”可能是你问题的根源 - 这太难说!尽管如果在析构函数的删除中失败,那么怀疑构造函数没有为缓冲区变量初始化内存是有道理的。 哦,关于复制构造函数的好点,在我发布相同内容后仅 42 分钟。哈哈【参考方案3】:

您还没有创建私有复制构造函数或任何默认构造函数。如果class Bucket 是通过这些隐式定义的方法之一构造的,则缓冲区要么未初始化,要么是由复制构造函数生成的复制指针。

Bucket 类的拷贝构造函数是Bucket(const Bucket &amp;B)——如果你没有显式声明拷贝构造函数,编译器会为你生成一个“幼稚”的拷贝构造函数。

特别是,如果此对象被分配、返回或以其他方式复制,则复制构造函数会将指针复制到新对象。最终,两个对象的析构函数都将尝试删除[]同一个指针,而第二次尝试将是双重删除,这是一种堆损坏。

我建议您将class Bucket 的复制构造函数设为私有,这将导致尝试复制构造产生编译错误。作为替代方案,您可以实现一个复制构造函数,为复制的 buffer 分配新空间。

同样适用于赋值运算符operator=

需要复制构造函数是Scott Meyer's excellent book, Effective C++: 55 Specific Ways to Improve Your Programs and Designs中的55个技巧之一:

这本书应该是所有 C++ 程序员的必读之书。

如果你添加:

类桶 /* 现有代码原样... */ 私人的: 桶()缓冲区=空; // 没有默认构造 Bucket(const Bucket &B) ; // 没有复制构造 Bucket& operator= (const Bucket &B) ; // 没有赋值

然后重新编译,你很有可能会发现你的问题。

还有另一种可能性:如果您的代码包含 newdelete 的其他用途,那么这些分配内存的其他用途可能会破坏定义的链表结构堆内存。在调用delete 期间通常会检测到这种损坏,因为delete 必须利用这些数据结构。

【讨论】:

抱歉,这是一个打字错误。构造函数在那里 - 我只是把它命名错了。现在我纠正了错误 本例中的复制构造函数为 Bucket(const class Bucket &B) 您是否还处理了默认构造函数“Bucket()”? 我真的不明白你所说的 new 和 delete 的其他用途是什么意思。错误发生在调用 delete[] 缓冲区的行上。在调用堆栈上,下一个位置是 afxmem.cpp 中的 delete[] 函数,从那里调用 delete(void* p)(也在 afxmem.cpp 中),然后在 dbgheap.c 中调用 _free_dbg,然后在 dgbheap 中调用 _free_dbg_nolock() .c 最后是 _CrtIsValidHeapPointer(也在 dgbheap.c 中)执行停止的地方 我的意思是你可能有一个大程序,而这个 Bucket 类只是其中的一小部分。您可能正在从程序的另一部分调用 new 和 delete,而该部分的缓冲区已损坏。

以上是关于MFC 多线程与 delete[] , d​​bgheap.c的主要内容,如果未能解决你的问题,请参考以下文章

MFC 多线程:AfxBeginThread 与 Boost.Thread?

mfc如何使用多线程搜索文件夹?

vc MFC 用AfxBeginThread()启动不了线程

VS2017编译错误:#error: Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll ve

VC中MFC创建了多线程,界面怎么还会有卡死现象,但程序在正确执行(可以执行出结果)

MFC多线程安全问题。