ios基于密钥创建写锁

时间:2014-08-20 09:07:34

标签: ios multithreading asynchronous grand-central-dispatch nslock

我知道使用dispatch_barrier_async来锁定给定资源,但在我的情况下,它不是一个好的候选者,因为我没有修改共享数据结构,而是修改磁盘上的资源而不想阻止整个队列,而不是一个给定的密钥,因为该操作可能需要很长时间。我不确定文件系统如何工作与同时从多个线程访问同一文件(按名称),并且在文档中找不到明确的答案,只是最佳实践。我想我想用“文件名”锁定 - 并且错过了方法“tryLock(key)

类似的东西:

-(void)readFileAtPath:(NSString *)path completion:(void(^)(NSData *fileData))completion
{
   dispatch_async(self.concurrentQueue,^{
       // acquire the lock for a given key and block until can acquire
       trylock(path);
       NSData *fileData = [self dataAtPath:path];
       unlock(path);
       completion(fileData);
   });
}

-(void)writeData:(NSData *)data toPath:(NSString *)path completion:(void(^)())completion
{
    dispatch_async(self.concurrentQueue,^{
        // if someone is reading the data at 'path' then this should wait - otherwise should write
        trylock(path);
        [data writeToFile:path atomically:YES];
        unlock(path);
        completion();
    });
}

编辑:

@synchronized这样做吗?这是一个正确的用例吗?

1 个答案:

答案 0 :(得分:2)

如果要创建“范围队列”,只需执行此操作即可。为每个文件创建一个串行队列,并使它们以并发队列为目标。它可能看起来像这样:

@interface Foo : NSObject
@property (readonly) dispatch_queue_t concurrentQueue;
@end

@implementation Foo
{
    NSMutableDictionary* _fileQueues;
    dispatch_queue_t _dictGuard;
}

@synthesize concurrentQueue = _concurrentQueue;

- (instancetype)init
{
    if (self = [super init])
    {
        _concurrentQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
        _dictGuard = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        _fileQueues = [[NSMutableDictionary alloc] init];
    }
    return self;
}

- (dispatch_queue_t)queueForFile: (NSString*)path
{
    __block dispatch_queue_t retVal = NULL;
    dispatch_sync(_dictGuard, ^{
        retVal = _fileQueues[path];
        if (!retVal)
        {
            retVal = dispatch_queue_create(path.UTF8String, DISPATCH_QUEUE_SERIAL);
            dispatch_set_target_queue(retVal, self.concurrentQueue);
            _fileQueues[path] = retVal;
        }
    });
    return retVal;
}

- (void)doStuff: (id)stuff withFile: (NSString*)path
{
    dispatch_queue_t fileQueue = [self queueForFile: path];
    dispatch_async(fileQueue, ^{
        DoStuff(stuff, path);
    });
}

@end

也就是说,这个每个文件队列的东西都有一点“代码味道”,特别是如果它旨在提高I / O性能。为了达到最佳性能,我觉得每个物理设备的队列比每个文件的队列更好。通常情况下,您作为开发人员比OS /系统框架更了解如何协调文件系统访问,因此您肯定希望在之前与之后进行测量,以确保此方法实际上提高了性能。当然,有时候你会知道操作系统不知道的东西,但是你可能想找到一种方法来给操作系统提供信息而不是重新发明轮子。在读写性能方面,如果您使用dispatch_io个通道来读取和写入文件,您将获得GCD所需的信息,以便最好地协调您的文件访问。

我也想到你也可能试图“保护自己的应用程序”。比如,如果您将磁盘用作缓存,多个任务可能同时访问该文件,则可能需要保护读取器不受其他编写者的影响。如果是这种情况,您可能希望寻找一些现有的框架,这些框架可能比滚动您自己的需求更好地满足需求。此外,在此用例中,您可能需要考虑在应用程序中管理作用域,并且只考虑mmap一个大文件,但此方法的成本/收益将取决于文件的粒度大小。 / p>

如果没有更多关于应用程序的背景,就很难说更多。

对于您的后续问题:@synchronized 可以用于实现此目的,但并不是没有太多与上面针对GCD方式发布的相同的机制。这样做的原因是@synchronized(foo)foo上通过身份(指针相等)而不是值相等(即-isEqual:)进行同步因此,具有值语义的NSStringNSURL(用于引用文件的两个最明显的对象)使它们成为不良候选者。使用@synchronized的实现可能如下所示:

@interface Bar : NSObject
@property (readonly) dispatch_queue_t concurrentQueue;
@end

@implementation Bar
{
    NSMutableDictionary* _lockObjects;
    dispatch_queue_t _dictGuard;
}

- (instancetype)init
{
    if (self = [super init])
    {
        _concurrentQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
        _dictGuard = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        _lockObjects = [[NSMutableDictionary alloc] init];
    }
    return self;
}

@synthesize concurrentQueue = _concurrentQueue;

- (id)lockForFile: (NSString*)path
{
    __block id retVal = NULL;
    dispatch_sync(_dictGuard, ^{
        retVal = _lockObjects[path];
        if (!retVal)
        {
            retVal = [[NSObject alloc] init];
            _lockObjects[path] = retVal;
        }
    });
    return retVal;
}

- (void)syncDoStuff: (id)stuff withFile: (NSString*)path
{
    id fileLock = [self lockForFile: path];
    @synchronized(fileLock)
    {
        DoStuff(stuff, path);
    }
}

- (void)asyncDoStuff: (id)stuff withFile: (NSString*)path
{
    id fileLock = [self lockForFile: path];
    dispatch_async(self.concurrentQueue, ^{
        @synchronized(fileLock)
        {
            DoStuff(stuff, path);
        }
    });
}

@end

你会看到我做了两个方法来做事,一个是同步的,另一个是异步的。 @synchronized提供了一个互斥机制,但不是异步调度机制,所以如果你想要并行,你仍然必须从GCD(或其他东西)获得它。它的长短是你< em>可以使用@synchronized来做这件事,这些日子不是一个好选择。它比同等的GCD机制慢得多。关于唯一有用的时间@synchronized这些天是作为实现递归锁定的语法快捷方式。也就是说,许多聪明的人认为递归锁定是一种反模式。 (有关原因的详细信息,请查看this link。)长期和短期是@synchronized不是解决此问题的最佳方式。