未使用 `deleteLater` 而直接使用 `delete` 導致問題

MarsCactus發表於2024-11-16

以下是一個完整的Qt程式碼示例,展示了未使用 deleteLater 而直接使用 delete 導致問題的情況,該示例涉及到一個簡單的多執行緒場景,主執行緒建立一個工作執行緒,工作執行緒完成任務後通知主執行緒,在物件刪除處理不當的情況下會出現崩潰等問題。

示例程式碼

#include <QObject>
#include <QThread>
#include <QDebug>
#include <QCoreApplication>

// 工作類,繼承自QObject,用於在工作執行緒中執行任務
class Worker : public QObject
{
    Q_OBJECT
public:
    Worker() {}

signals:
    // 任務完成後傳送訊號,傳遞一個整數值給主執行緒
    void resultReady(int result);

public slots:
    void doWork()
    {
        // 模擬耗時任務,這裡簡單睡眠一下
        QThread::sleep(2);
        int result = 42;
        // 傳送任務完成的訊號
        emit resultReady(result);
    }
};

// 控制器類,用於管理工作執行緒和處理結果
class Controller : public QObject
{
    Q_OBJECT
public:
    Controller()
    {
        // 建立工作物件和執行緒物件
        worker = new Worker();
        thread = new QThread();

        // 將工作物件移動到工作執行緒中
        worker->moveToThread(thread);

        // 連線執行緒啟動訊號和工作物件的工作函式
        connect(thread, &QThread::started, worker, &Worker::doWork);

        // 連線工作物件的完成訊號和自身的處理結果函式
        connect(worker, &Worker::resultReady, this, &Controller::handleResult);

        // 啟動執行緒
        thread->start();
    }

    ~Controller()
    {
        // 這裡直接delete工作物件,會導致問題,應該使用worker->deleteLater()
        delete worker;
        thread->quit();
        thread->wait();
        delete thread;
    }

private slots:
    void handleResult(int result)
    {
        qDebug() << "Received result: " << result;
    }

private:
    Worker *worker;
    QThread *thread;
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    {
        Controller controller;
    }
    return app.exec();
}

#include "main.moc"

問題分析

在上述程式碼中,Controller 類負責管理工作執行緒和處理工作執行緒返回的結果。在 Controller 的解構函式中,直接使用 delete 來刪除 worker 物件存在以下嚴重問題:

  • 物件生命週期衝突
    Controller 的解構函式被呼叫時(例如在 main 函式中 Controller 例項所在的作用域結束時),如果此時工作執行緒中的 worker 物件還在執行 doWork 函式(比如還處於 QThread::sleep(2) 的睡眠階段或者剛結束睡眠正準備傳送 resultReady 訊號),直接呼叫 delete 去刪除 worker 物件,就違背了物件的正常生命週期。因為 worker 物件在工作執行緒中仍處於活動狀態,其內部資源還在被使用,這樣強制刪除會導致工作執行緒後續可能對已經釋放的記憶體進行訪問(例如嘗試去傳送 resultReady 訊號時,相關的物件記憶體已經不存在了),從而引發程式崩潰,一般會出現類似“段錯誤”的報錯提示。

  • 執行緒安全問題
    由於是在多執行緒環境下,主執行緒(也就是 Controller 所在的執行緒,執行解構函式的執行緒)直接刪除了 worker 物件,而工作執行緒並不知道這個情況,這就造成了執行緒間的同步問題。如果工作執行緒後續繼續按照正常流程去操作 worker 物件(比如觸發訊號等),就會出現對非法記憶體區域的訪問,破壞了多執行緒程式設計中對共享物件訪問的安全性要求,導致程式出現未定義行為甚至崩潰。

正確的做法應該是在合適的時機呼叫 worker->deleteLater(),例如可以在 handleResult 槽函式中判斷任務已經徹底完成,不會再有對 worker 物件的操作後,呼叫 worker->deleteLater() 來安全地安排 worker 物件的刪除,讓Qt的事件迴圈在確保安全的情況下(比如工作執行緒相關的所有操作都完成後)再執行刪除操作,避免上述出現的各種問題,保障程式的穩定執行。

希望這個示例能夠清晰地展示未合理使用 deleteLater 所帶來的問題,你如果還有其他疑問或者想進一步瞭解相關知識,可以隨時問我。

相關文章