You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

对象生命周期与多线程:已销毁对象仍被线程正常访问的原因

为什么销毁对象后线程还能正常操作它?

这是个典型的**未定义行为(Undefined Behavior)**场景,咱们一步步拆解背后的原因:

1. delete到底做了什么?

当你调用delete data时,操作系统只是收到了一个“这块内存现在可以被重新分配”的通知——它并不会立刻清空内存里的内容,也不会把指针标记为无效。在这块内存还没被其他程序/线程占用之前,原来的Data对象数据大概率还留在原地。你的工作线程此时去访问这块内存,碰巧还能读到旧值、甚至修改,所以看起来“一切正常”。

但这完全是运气,不是合法操作!

2. 为什么这极度危险?

未定义行为的可怕之处在于它的不可预测性:

  • 如果操作系统在某个时刻把这块内存分配给了其他任务,你的线程再去读写就会直接触发**段错误(Segmentation Fault)**崩溃。
  • 更隐蔽的情况是,你的线程修改了这块已释放的内存,刚好覆盖了其他程序的数据,会导致莫名其妙的bug,排查起来极其困难。

你的代码里,oops2()函数退出前就delete了Data对象,此时工作线程还在运行,data指针已经变成了悬空指针(Dangling Pointer),后续所有对它的操作都是非法的。

3. 正确的做法是什么?

要确保线程访问的对象生命周期至少和线程一样长,常见的解决方案有几种:

方案一:等待线程结束后再释放对象

调整oops2()的逻辑,把delete放到线程join之后:

void oops2() {
    Data* data = new Data(100);
    func2 my_func(data);
    my_thread = std::thread(my_func);
    my_thread.join(); // 先等线程执行完
    delete data;      // 再释放对象
}

方案二:用智能指针共享所有权

使用std::shared_ptr来管理Data对象,让主线程和工作线程共享所有权,直到最后一个使用者结束才自动释放内存:

#include <memory>

class Data { /* 保持原定义不变 */ };

struct func2 {
    std::shared_ptr<Data> data;
    func2(std::shared_ptr<Data> data_): data(data_){}
    void operator()() {
        printf("Address of func2 data is %p\n", data.get());
        for (int j=0;j < 1000000;++j) {
            print_val(data.get());
            set_val(j, data.get());
        }
    }
};

void oops2() {
    auto data = std::make_shared<Data>(100);
    func2 my_func(data);
    my_thread = std::thread(my_func);
    // 无需手动delete,最后一个shared_ptr销毁时自动释放
}

方案三:把对象所有权转移给线程

让工作线程负责在执行结束后释放对象:

struct func2 {
    Data* data;
    func2(Data* data_): data(data_){}
    void operator()() {
        printf("Address of func2 data is %p\n", data);
        for (int j=0;j < 1000000;++j) {
            print_val(data);
            set_val(j, data);
        }
        delete data; // 线程执行完自行释放
    }
};

void oops2() {
    Data* data = new Data(100);
    func2 my_func(data);
    my_thread = std::thread(my_func);
    // 主线程不再delete
}

总结

你看到的“正常运行”只是未定义行为的一种表现,绝对不能依赖这种情况。编写多线程代码时,必须严格保证线程访问的对象生命周期是安全的,避免悬空指针和未定义行为。

内容的提问来源于stack exchange,提问作者Oleg Kuzenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:22:12