0

几个月来,我在我的应用程序中遇到了这个问题。我尝试了许多自制的解决方案,并将解释我在这里工作的内容,但我希望有人能提出我错过的更好的解决方案。

基本问题是:我(可能)有数千个项目需要我的应用随时访问。NSMutableDictionary 通常是我表示每个项目的第一种方法,因为每个项目可能有几个到数百个属性。但是其余的要求使事情变得棘手:

  1. 每个项目都可能被任何线程读取或写入
  2. 每个项目都需要存储到磁盘,以便可以在会话之间检索
  3. (可能)有太多的项目(和太多的数据)一次将它们全部放在内存中可能会导致内存问题

我想使用 CoreData,因为 Apple 非常喜欢它,但我遇到了很多问题。每个 Item 都没有确定的结构,因此没有很好的方法来构建数据模型。此外,查询数据会导致单个 .sqlite 文件成为瓶颈,这意味着当许多线程同时尝试检索项目时,等待时间(滞后)会很快变得荒谬。

我有一个可行的解决方案,但它有问题。这是一段代码,我将在下面解释它的作用

- (NSObject*) getValue:(NSString*)key {
    @synchronized(self) {
        if(!_cached_obj) { // private variable in this object
            _cached_obj = [self loadFromDisk]; // simply loads the NSDictionary from a file
        }
        _last_access = time(nil);//don't release for a while
        return [_cached_obj valueForKey:key];
    }
}
- (void) setValue:(NSObject*)value forKey:(NSString*)key {
    @synchronized(self) {
        [self getValue:key];//ensures the cache is active
        [_cached_obj setValue:value forKey:key];
        _needs_save = true;
    }
}
- (void) clean {
    if(!_cached_obj)
        return;
    @synchronized(self) {
        if(_needs_save)
        {
            [self writeToFile];//writes the cache obj to a file
            _needs_save = NO;
        }

        NSTimeInterval elapsed = time(nil) - _last_access;
        if(elapsed > 20)
        {
            [_cached_obj release];
            _cached_obj = nil;
        }
    }
}
  • 当我需要来自某个项目的数据时,会调用 getValue 函数。它尝试使用缓存对象(NSMutableDictionary)。如果缓存的对象为 NULL,则在返回之前从磁盘加载对象
  • setValue 函数按预期工作,但也设置了保存标志
  • “清洁”功能由后台线程在 10 秒计时器上运行。这负责将项目保存到磁盘并取消缓存数据以节省内存。

我不喜欢我的方法的一点是,基于我对@synchronized 的使用,信号量有很多等待。有时,这也意味着主线程在等待磁盘读/写时被阻塞,这很痛苦。

我是否缺少更好的数据结构或存储机制?

谢谢!


编辑:更多信息:“getValue”函数返回的速度也非常重要,即使它没有阻塞主线程。例如,考虑我在后台线程上搜索 10k 个项目的场景。我需要从 10k 个对象中的每一个中获取一个值一次。使用我目前的机制,它可以工作,但是从磁盘加载每个非缓存对象非常耗时,并且最终在我的 iPhone 4 上需要大约 20 秒。我知道这可能只是“我必须付出的代价。 " 但是,也许将数据存储在更小的块中会有所帮助?例如,不要将整个项目存储为字典,而是存储为不同对象的集合。

4

1 回答 1

1

据我了解,您分析了您的应用程序,并且配置文件显示 @synchronize 块是最大的性能瓶颈。对?

好吧,我并不过分惊讶:正如您指出的那样,您在持有互斥锁的同时读写文件。此外,您同时只允许一个线程,而您可以轻松地允许多个读取器或一个写入器访问您的缓存。

确定的锁定操作:

  • 获取值 -> 在缓存中获取值,如果不在缓存中,则在磁盘上获取值,将值放入缓存中
  • 设置值 -> 在缓存中获取值,如果不在缓存中,则在磁盘上获取值,将值放入缓存中,将新值放入缓存中
  • 清理 -> 保存缓存,清空缓存

那么,基本操作是:

  • 在缓存中获取值
  • 在磁盘上获取价值
  • 将值放入缓存
  • 保存缓存
  • 空缓存

确定这些简单操作的并发性非常容易,然后重新设计锁以确保一切都很好地相互配合。

您可以允许多个读取器或一个写入器访问缓存。一个线程可以读取(或写入)磁盘,而无需锁定缓存。从磁盘读取的值将在稍后作为写入器设置在缓存中。所以一个用于缓存的读写锁和一个用于文件的互斥锁。设定值序列也有点令人费解。我看不到从文件中读取旧值以立即替换它的意义。如果您需要准备好缓存数据结构,只需确保它们没有触发文件操作即可。

所有这些也可以使用 GCD 来实现,避免大部分锁(如果不是全部的话)。

在不引入大量复杂性或更改应用程序线程模型的情况下,有足够的空间来减少冲突。我认为 GCD 提供了更多机会,但您必须考虑队列和操作而不是线程,这乍一看并不总是那么容易。

我不会说重做锁就足够了,您可能还必须改进数据读取和保存到磁盘的方式,但从锁开始。你可能会感到惊讶。

于 2012-07-25T01:01:49.953 回答