我只是想知道如何使用C98解决这个问题(所以没有shared_ptr):


我的班级很大,有很多数据(BigData
我有一个DataStorage类,可以在map中跟踪该数据
数据不太可能更改,但是可以
我在多线程环境中

 class BigData;

 class DataStorage{
  public:
       const BigData *getStuff(int which_one) const{
          lock_guard<mutex> guard(mut);
           return &myReallyBigDatas[which_one]; // thanks Donghui
       }
  protected:
     mutable mutex mut;
     map<int,BigData> myReallyBigDatas;
 }



(如Keith M所建议。我没有提到我要解决的问题)

我知道这段代码是错误的,从我的角度来看,我想解决两个主要问题:


如果由于删除或覆盖了地图上的此位置而使对象返回了消失,则该对象将消失(我将有一个指针指向无处,UB!)。
如果返回的BigData实例被修改


当然,我想找到一种解决方案,避免将来其他人更改此代码时出错

我想出了以下解决方案:


包括BigData的互斥量。解决问题2
更改函数以返回一个真实的对象,而不是返回一个指针(这非常好,但是缺点是在复制一个非常大的类时会降低性能)这解决了两个问题
实现我自己的shared_ptr类(不能使用C11也不能使用boost)。解决问题1
在类DataStorage上创建一个锁定/解锁(真糟糕!)。这解决了两个问题
保持错误并祈祷很多。这个....


我敢肯定,很多人在遗留代码中找到了这样的一段代码,并且我想找到最好的解决方案。

附言我知道我正在使用C11互斥锁。我的真实代码没有它,但是用这种方式编写示例代码更容易。

最佳答案

实际上,shared_ptrmutex彼此非常独立,并且您可能两者都需要-shared_ptr用于保证恰好一个资源的释放,而mutex用于保证没有并发的读/写操作(或并发读取也取决于互斥锁的类型)。

使用shared_ptr基本上意味着没有数据的单个所有者。尽管这可以管理(例如引用计数),但它并不总是最佳的解决方案(记住循环依赖,对weak_ptr的需求等)-有时最好找出一个资源的单一所有者来负责在不再需要它时(例如,如果您有一个工作线程池,可能是产生其他线程的那个线程)取消分配它-当然,您必须保证它的寿命比其他线程更长,以使所有人均可访问的数据。因此,您有几个用于管理对象生命周期的选项:


在不使用整个库的情况下“借用” boost / C ++ 11标准库/ Loki /其他存在的实现中的代码(签出许可证以验证是否可以使用它们)-您可能需要对以下代码进行更改他们
编写自己的智能指针-辛苦且仅适用于专业人士-完全不建议
选择一个资源的所有者-这就是我的建议


当涉及访问冲突管理时,基本上有两种方法:


使用某种锁来管理它们(我假设您有一个可用的锁)
通过仅允许单个线程写入对象来避免它们。通常可能需要的其他请求将不得不请求“所有者线程”进行写操作。这种方法非常适合单一资源所有者,但是与典型的共享内存多线程相比,它更是一种参与者模型,因此可能很难在遗留应用程序中引入。


您可以将锁与任何树内存管理方法一起使用,单写入者方法最适合单所有者。请注意,这是范式的重大改变,可能需要大量工作来实现消息队列和工作程序之类的东西。

如果您已经拥有基础结构(队列,工作程序等),我建议您考虑采用单所有者,单编写者的方法,否则带锁的单所有者可能是一个很好的解决方案。如果您无法选择一个所有者,请从现有库中提取代码-不要自己编写代码,因为这会导致一些错误,并且在多线程环境中进行内存管理非常困难

编辑1

既然您已经澄清了问题,那么我认为答案有点过于笼统,因此我将添加更多详细信息。

您可以做的最简单的事情是返回一个BigData&而不是BigData*-那时没有人可以删除它(当然可以,但是实际上所有内容都在C ++中)。否则,您还可以:


仅允许一个线程使用单个BigData实例-(例如,通过存储带有已用std::map<int, thread_id>的信息的其他BigData-仅在不需要从多个线程同时访问同一实例的情况下)
返回类似BigDataProxy而不是BigData的内容-Proxy应该具有特殊的资源删除功能,然后由“最后一个感兴趣的人”执行-实际上,这只是shared_ptr的特例,但可能更容易实现。


从概念上讲,Proxy类似于(伪代码-忽略私人/公共成员等):

class BigDataProxy {
  public:
  BigDataProxy(data_, instanceId_): data(data_), instanceId(instanceId_) {
    std::lock_guard l(data.mutex);
    data.interestedThreads[instanceId].insert(this_thread::thread_id);
  }

  ~BigDataProxy() {
    std::lock_guard l(data.mutex);
    data.interestedThreads[instanceId].remove(this_thread::thread_id)
    if(data.interestedThreads[instanceId].empty() && data.toDelete.contains(instanceId) {
      data.elems.remove(instanceId);
      data.toDelete.remove(instanceId);
    }
  }

  BigData& operator*() {
    return data.elems[instanceId];
  }
  void remove() {
     std::lock_guard l(data.mutex);
     data.toDelete.add(instanceId);
  }

  private:
    DataStorage& data;
    int instanceId;
}


DataStorage中的更改要求它看起来像这样:

class DataStorage {

  std::mutex mutex;
  std::map<int, BigData> elems;
  std::set<int> toDelete;
  std::map<int, std::set<thread_id> > interested_threads;
}


请注意,这是伪代码,在这里异常处理将很困难。

关于c++ - 访问类专有的大量数据的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40183422/

10-09 02:50