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

将析构调用转移至专用线程:C++代码实现有效性问询

问题:将shared_ptr析构开销转移至专用线程的Cleaner类是否可行?

我在优化C++代码时遇到析构调用耗时过长的问题,为此编写了Cleaner类尝试将析构操作转移至其他线程,初步测试可行。现在想确认:

  • 该代码是否能如预期般将shared_ptr中的数据所有权从调用线程转移至专用线程,从而将内存释放开销转移至该线程?
  • 是否存在重大隐患或副作用?

代码实现

#pragma once

#include <queue>
#include <mutex>
#include <memory>
#include <functional>

namespace test {

  class Cleaner {
    struct State {
      std::queue<std::shared_ptr<void>> queue;
      std::mutex mtx;
      std::condition_variable condition;
      bool cancelled;
      bool done;
    };
  public:
    Cleaner()
      :
      _state(std::make_shared<State>()),
      _thread(&runCleanerloop, _state)
    {
    }

    ~Cleaner() {
      {
        std::lock_guard<std::mutex> lock(_state->mtx);
        _state->done = true;
      }
      _state->condition.notify_all();
      _thread.join();
    }

    template <typename T>
    void run(std::shared_ptr<T>& data) {
      {
        std::lock_guard<std::mutex> lock(_state->mtx);

        // Push a copy to the queue to be released by the thread.
        _state->queue.push(data);

        // Reset the input in the caller's thread.
        data.reset();
      }
      _state->condition.notify_all();
    }

    static void runCleanerloop(
      const std::shared_ptr<State>& state
    ) {
      std::shared_ptr<void> data;

      bool done = false;

      while (true) {
        // Wait for data to appear in queue.
        {
          std::unique_lock<std::mutex> lock(state->mtx);

          state->condition.wait(lock, [&state] {
            return state->cancelled || state->queue.size() || state->done;
          });

          if (state->cancelled || (!state->queue.size() && state->done)) {
            done = true;
          }
          else {
            data = state->queue.front();

            state->queue.pop();
          }
        }
        state->condition.notify_all();

        if (done) {
          return;
        }

        // Release the data inside this thread.
        data.reset();
      }
    }
  private:
    std::shared_ptr<State> _state;
    std::thread _thread;
  };
}

预期用法

#include "cleaner.h"

void test() {
  Cleaner cleaner;
     
  std::shared_ptr<Data> current_data;
  while (true) {
    current_data = createData();

    /* Do something */
      
    // This will reset the current_data ptr, but the actual destructor-call
    // will be done inside the Cleaner thread.
    cleaner.run(current_data);
  }
}

补充说明:当然,相比此类实现,更优方案是设计合理的内存管理系统,避免循环中重复分配内存;若存在其他shared_ptr持有数据,则由于引用计数未归零,专用线程不会销毁数据。


回答

一、是否达到预期效果?

是的,这个实现确实能将析构开销转移到Cleaner线程:

  • 调用cleaner.run(current_data)时,会先把shared_ptr的副本推入队列,此时引用计数+1;随后在调用线程reset原指针,引用计数回到1(仅队列中的副本持有)。
  • 当Cleaner线程取出队列中的shared_ptr并reset时,引用计数归零,析构函数和内存释放会在Cleaner线程执行,完全避开了调用线程的开销。
  • 你补充说明的「若存在其他shared_ptr持有数据,则专用线程不会销毁数据」也是正确的,因为此时引用计数不会归零,析构会等到最后一个shared_ptr被销毁时才触发。

二、存在的隐患与副作用

  • 队列堆积与内存占用风险:如果调用线程产生待销毁对象的速度远快于Cleaner线程的处理速度,队列会持续堆积,导致内存占用飙升,甚至触发OOM。尤其是在循环创建销毁的场景中,这个问题会被放大。
  • 不必要的条件变量通知:
    • run函数每次push后调用notify_all(),实际上notify_one()就足够唤醒等待的Cleaner线程,notify_all()会带来额外的线程唤醒开销。
    • Cleaner线程在每次处理完一个对象后调用的notify_all()完全多余,没有其他线程等待该操作完成,只会徒增开销。
  • 无效的cancelled状态:代码中定义了cancelled标志,但没有提供外部触发的接口,属于无效代码,容易造成维护混淆。
  • 线程安全边界问题:虽然Cleaner的析构逻辑是安全的,但如果多线程同时调用run并同时触发Cleaner析构,需要确保Cleaner对象的生命周期管理正确,避免析构时还有线程调用run。
  • 性能权衡问题:每个run调用都需要加锁、操作队列,这些操作本身有一定开销。如果单个对象的析构开销很小,转移到其他线程的收益可能抵不上锁和队列操作的开销,反而降低整体性能。

三、优化建议

  • 将notify_all()替换为notify_one(),减少不必要的线程唤醒。
  • 移除无用的cancelled状态,或者补充对应的取消接口(比如cancel()方法)。
  • 考虑给队列设置最大容量,当队列满时让调用线程阻塞或根据业务场景选择丢弃策略,避免内存无限增长。
  • 可以批量处理队列中的对象,减少锁的竞争次数(比如一次取出多个对象再统一销毁)。
  • 优先从内存管理架构上优化,比如使用对象池复用对象,从根源上减少频繁创建销毁的开销,这比转移析构开销的收益更大。

内容的提问来源于stack exchange,提问作者4-bit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 23:26:07