访问一个类私有的大量数据的正确方法是什么?

时间:2016-10-21 18:38:50

标签: c++ multithreading c++98

我只是想知道如何使用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.所建议的,我没有提到我试图解决的问题)

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

  1. 如果对象返回desapear,因为地图上的这个位置被删除或覆盖(我将指向没有指向的地方,UB!)
  2. 如果修改了返回的BigData实例
  3. 当然,我想找到一个解决方案,以避免其他人改变此代码的未来错误

    我提出了这些解决方案:

    1. 在BigData中包含互斥锁。这解决了问题2
    2. 更改函数以返回一个真实的对象,而不是一个指针(这是非常好的,但它具有杀死性能的缺点,而它正在制作一个非常大的类的副本)这解决了这两个问题
    3. 实现我自己的shared_ptr类(不能使用C11也不能使用boost)。这解决了问题1
    4. 在类DataStorage上创建一个锁定/解锁(非常糟糕!)。这解决了这两个问题
    5. 保持错误,祈祷很多。此....
    6. 我很确定很多人在遗留代码中找到了这样的代码,我想找到最好的解决方案。

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

2 个答案:

答案 0 :(得分:2)

您的想法是正确的:保持数据私密,并提供获取数据或部分数据的getter。

您的代码中有错误。 getStuff()的签名返回指向BigData的指针,但实现返回对BigData的引用。类型不匹配。

你是对的,因为你不想制作BigData的副本。所以你有三个选择:

  • 返回myReallyBigDatas [which_one]; //返回BigData&amp;
  • return&amp; myReallyBigData [which_one]; //返回BigData *
  • 返回myReallyBigDatas.find(which_one); // return map :: iterator

答案 1 :(得分:2)

实际上,shared_ptrmutex相互独立,您可能需要两者 - shared_ptr用于保证一个资源的完全释放,而mutex是用于保证没有并发读/写操作(或并发读取,取决于互斥锁的类型)。

使用shared_ptr基本上意味着没有单一的数据所有者。虽然这是可以管理的(例如引用计数),但它并不总是最好的解决方案(记住循环依赖关系,需要weak_ptr等) - 有时候最好找出一个资源的单一所有者,当它不再需要时将负责解除分配(例如,如果你有一个工作线程池,它可能是产生其他线程的那个) - 当然,那么你必须保证它的寿命比其他人长,以使每个人都可以访问数据。因此,您可以使用多种方法来管理对象的生命周期:

  • &#34;借用&#34;来自boost / C ++ 11标准库/ Loki /某些其他现有实现的代码(检查许可证以验证您是否可以使用它们)而不使用整个库 - 您可能需要对它们进行更改
  • 编写自己的智能指针 - 很难,只有专业人士 - 完全不推荐
  • 选择资源的单一所有者 - 这是我推荐的

在访问冲突管理方面,基本上有两种方法:

  • 使用某种锁定管理它们(我假设你有一个锁定)
  • 通过只允许一个线程写入对象来避免它们。其他通常可能需要请求来自&#34;所有者线程的#34;。这种方法非常适合资源的单一所有者,但它比典型的共享内存多线程更像是一个actor模型,因此很难在遗留应用程序中引入。

您可以将锁与任何树内存管理方法一起使用,单作者方法最适合单一所有者。请注意,这是范例的重大更改,可能需要执行大量工作,如消息队列和工作人员。

如果您已经拥有基础设施(队列,工作人员等),我建议您查看单一所有者,单一作者的方法,否则带锁的单一所有者可能是一个很好的解决方案。如果您无法选择单个所有者,请从现有库中提取代码 - 不要自己编写代码,因为您会犯一些错误,多线程环境中的内存管理确实< / em> hard

编辑1

既然您已经澄清了这个问题,我觉得答案有点过高,所以我会补充一些细节。

你可以做的最简单的事情是返回一个BigData&而不是BigData* - 没有人应该删除它(当然它是可能的,但实际上一切都在C ++中)。否则,你也可以:

  • 仅允许单个线程使用单个BigData实例 - (例如,通过存储有关已使用std::map<int, thread_id>的信息的其他BigData - 仅当您不需要并发访问时来自多个线程的同一个实例
  • 返回BigDataProxy而不是BigData之类的内容 - Proxy应该有一个特殊的资源删除功能,然后由&#34;最后一个感兴趣的&#34执行; - 这实际上只是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;
}

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