由于删除c++Qt多线程应用程序中的指针而导致崩溃

Crash due to delete a pointer in c++ Qt multithread application

本文关键字:指针 崩溃 应用程序 多线程 于删除 删除 c++Qt      更新时间:2023-10-16

我有一个多线程应用程序,它使用线程池,所以有10个线程运行相同的run()函数,如下所示:

run(){
...
SetTileAt(p, tile);
...
ClearPointsNotIn(list);
...
}
void TileMatrix::ClearPointsNotIn(QList<Point>list)
{
    removals.clear();
    mutex.lock();
    foreach(Point p, matrix.keys())
    {
        if(!list.contains(p))
        {
            removals.append(p);
        }
    }
    mutex.unlock();
    foreach(Point p,removals)
    {
        Tile* t=TileAt(p);
        if(t!=0)
        {
            mutex.lock();
            delete t;
            t=0;
            matrix.remove(p);
            mutex.unlock();
        }
    }
    removals.clear();
}
void TileMatrix::SetTileAt(const Point &p, Tile* tile)
{
     mutex.lock();
     Tile* t=matrix.value(p,0);
     if(t!=0)
        delete t;
     matrix.insert(p,tile);
     mutex.unlock();
}
Tile* TileMatrix::TileAt(const Point &p)
{
 Tile* ret;
 mutex.lock();
 ret=matrix.value(p,0);
 mutex.unlock();
 return ret;
}

当我运行应用程序时,它有时会在删除t的部分崩溃,我检查了t的值,当时它似乎是t=0,但是指向的内容完全是垃圾。我天真地猜测这是一个"删除已删除的指针问题"。但我不太确定这是怎么发生的,我如何修改代码来防止它?请注意,TileAt中的muetex与ClearPointsNotIn()中的一个muetex可能会创建死锁。。。

这是可能发生的事情,而且似乎不时发生:

很有可能,就在您在TileMatrix::ClearPointsNotIn中获得指针t之后,另一个线程将锁定在TileMatrix::SetTileAt函数内的mutex.lock();。此时,matrix.value(p,0);可以返回与之前在控制TileMatrix::ClearPointsNotIn的线程中返回的TileAt(p);完全相同的指针。然后删除TileMatrix::SetTileAt中的t并解锁。与此同时,在运行TileMatrix::ClearPointsNotIn函数的线程中,您在t中有一个已经删除的指针(因为您在另一个线程中删除了它),当您对其调用delete时,应用程序将崩溃。

这被称为竞赛条件。

我建议将mutex.lock()语句从TileMatrix::ClearPointsNotIn移到foreach之后、Tile* t=TileAt(p);之前。我还建议在删除指针后,也将0NULL分配给它。这样,如果指针之前设置为0NULLif(t!=0)将阻止执行线程执行if内的块。请阅读此处和此处了解更多详细信息。

如果不够清楚,请告诉我,我可以提供更多细节。

正如刘柳所提到的,由于互斥对象对TileAt的保护不足,您存在竞争条件。

ClearPointsNotIn应该受到互斥锁的整体保护。点列表也应该通过const引用传递,而不是通过值传递。您应该使用RAII互斥锁。它可能看起来如下:

void TileMatrix::ClearPointsNotIn(const QList<Point> & list)
{
    removals.clear();
    QMutexLocker lock(&mutex);
    foreach(Point p, matrix.keys())
    {
        if (!list.contains(p)) removals << p;
    }
    foreach(Point p, removals)
    {
        Tile* t = TileAt(p);
        delete t;
        if (t) matrix.remove(p);
    }
}

此外,假设瓦片的删除没有需要持有互斥体的副作用,您可以进行重构以从互斥体下删除瓦片:

class TileMatrix {
  ...
  QList<Tile*> deadTiles;
  ...
};
// Variant 1
void TileMatrix::ClearPointsNotIn(const QList<Point> & list)
{
    QMutexLocker lock(&mutex);
    deadTiles.clear();
    foreach(Point p, matrix.keys())
    {
        if (list.contains(p)) continue;
        Tile* tile = TileAt(p);
        if (tile) { 
          deadTiles << tile;
          matrix.remove(p);
        }
    }
    lock.unlock();
    foreach(Tile* tile, deadTiles) delete tile;
}
// Variant 2
void TileMatrix::ClearPointsNotIn(const QList<Point> & list)
{
    QMutexLocker lock(&mutex);
    foreach(Point p, matrix.keys())
    {
        if (list.contains(p)) continue;
        Tile* tile = TileAt(p);
        if (!tile) continue;
        matrix.remove(p);
        delete tile;
    }
}

您应该了解变体1是否确实比变体2快。