如何实现FooManager中shared_ptr容器的更新及优化方案?
实现
FooManager::ComputeContinues 的核心逻辑 首先得明确,咱们的核心目标是同步更新farray和fmap,同时安全终止不需要继续运行的Foo实例的线程——线程安全是这里的重中之重,不能直接删对象,得先让线程优雅退出。
第一步:给Foo加线程终止的控制方法
先给Foo类补充两个方法,用来通知线程停止并等待它结束:
class Foo { public: // 其他已有成员... void RequestStop() { // 用原子标志通知线程退出循环 stop_flag.store(true, std::memory_order_relaxed); } void WaitForStop() { if (thread_.joinable()) { thread_.join(); } } // 举个线程函数的例子,里面要检查停止标志 void RunThread() { while (!stop_flag.load(std::memory_order_relaxed)) { // 这里是Foo的业务逻辑 } } private: std::atomic<bool> stop_flag{false}; boost::thread thread_{&Foo::RunThread, this}; // 其他私有成员... };
第二步:实现ComputeContinues的容器更新逻辑
接下来在FooManager里写具体的更新代码,思路是先收集要保留的实例,终止要删除的实例的线程,再同步更新两个容器:
void FooManager::ComputeContinues(const std::vector<int>& foo_continues) { // 1. 先筛选要保留的实例,同时终止待删除实例的线程 std::vector<std::shared_ptr<Foo>> new_farray; std::unordered_set<size_t> keep_ids; for (size_t idx = 0; idx < farray.size(); ++idx) { auto foo_ptr = farray[idx]; if (foo_continues[idx] > 0) { // 把要保留的实例加入新数组,记录它的ID new_farray.push_back(foo_ptr); keep_ids.insert(foo_ptr->GetID()); } else { // 先通知线程停止,再等待它结束 foo_ptr->RequestStop(); foo_ptr->WaitForStop(); // shared_ptr会自动管理内存,这里不用手动释放 } } // 2. 替换farray:用swap比直接赋值更高效,还能自动清理旧数据 farray.swap(new_farray); // 3. 清理fmap:删除不在保留列表里的条目 auto map_it = fmap.begin(); while (map_it != fmap.end()) { if (keep_ids.find(map_it->first) == keep_ids.end()) { map_it = fmap.erase(map_it); } else { ++map_it; } } }
这里的几个关键细节:
- 用
swap更新farray,避免不必要的内存拷贝和分配 - 先终止线程再删除容器条目,防止出现悬空线程或者资源泄漏
- 用
keep_ids同步两个容器的保留状态,确保farray和fmap里的实例完全一致
替代方案:简化容器结构,避免双重维护
同时维护farray和fmap确实容易出现一致性问题,这里给你几个更简洁的替代思路:
方案1:只保留std::map(或unordered_map)
如果不需要按索引快速访问实例,完全可以只保留fmap。如果需要对应原来的索引,可以额外维护一个index_to_id的数组,用来记录每个旧索引对应的Foo ID:
class FooManager { public: std::unordered_map<size_t, std::shared_ptr<Foo>> fmap; // unordered_map访问更快 std::vector<size_t> index_to_id; // 记录原farray索引对应的Foo ID // 添加Foo的逻辑修改一下: void AddFoo(std::shared_ptr<Foo> foo) { fmap.emplace(foo->GetID(), foo); index_to_id.push_back(foo->GetID()); } };
这样在ComputeContinues时,就可以通过index_to_id找到每个索引对应的Foo ID,决定保留或删除,之后还可以清理index_to_id为下一轮使用。
方案2:用boost::thread::group管理线程
你之前觉得自己的方式比thread::group直观,但其实它能帮你简化线程的批量管理:
class FooManager { public: std::unordered_map<size_t, std::shared_ptr<Foo>> fmap; boost::thread_group thread_group; // 添加Foo时把线程加入group: void AddFoo(std::shared_ptr<Foo> foo) { fmap.emplace(foo->GetID(), foo); thread_group.add_thread(&foo->thread_); // 可以给Foo加个getter方法获取线程 } // 批量终止线程的话: void StopThreads(const std::vector<size_t>& ids_to_stop) { for (auto id : ids_to_stop) { auto it = fmap.find(id); if (it != fmap.end()) { it->second->RequestStop(); } } thread_group.join_all(); // 等待所有标记停止的线程结束 } };
这样不用手动逐个join线程,thread_group会帮你处理,减少出错的概率。
方案3:用单个容器封装两种访问方式
如果确实需要同时按ID和顺序访问,可以自定义一个小的封装类,内部维护vector和unordered_map,对外提供统一的添加、删除接口,确保两种容器的修改是同步的,比如:
template<typename T, typename Key> class IndexedMap { public: void Add(Key id, std::shared_ptr<T> item) { vec_.push_back(item); map_.emplace(id, item); } void Remove(Key id) { auto map_it = map_.find(id); if (map_it != map_.end()) { // 从vector中删除对应的元素 vec_.erase(std::remove(vec_.begin(), vec_.end(), map_it->second), vec_.end()); map_.erase(map_it); } } // 提供按索引和按ID的访问方法... private: std::vector<std::shared_ptr<T>> vec_; std::unordered_map<Key, std::shared_ptr<T>> map_; };
然后FooManager里直接用这个IndexedMap<Foo, size_t>来替代原来的两个容器,这样就不用手动同步了。
内容的提问来源于stack exchange,提问作者BoltzmannBrain
相关产品推荐
相关产品推荐

