如果你想创建“作用域队列”,那就去做吧。为每个文件创建一个串行队列,并让它们针对您的并发队列。它可能看起来像这样:
@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 性能。就在我的脑海中,为了获得最佳性能,感觉每个物理设备都有一个队列比每个文件一个队列要好。通常情况下,您作为开发人员并不比操作系统/系统框架更了解如何协调文件系统访问,因此您肯定需要在之前和之后进行测量,以确保这种方法实际上可以提高您的性能。当然,有时您会知道一些操作系统不知道的东西,但您可能想寻找一种方法为操作系统提供这些信息,而不是重新发明轮子。在读写性能方面,如果你使用dispatch_io
读取和写入文件的通道,您将为 GCD 提供最佳协调文件访问所需的信息。
我还想到你也可能试图“保护应用程序本身”。例如,如果您将磁盘用作缓存,多个任务可能同时访问文件,您可能需要保护读取器免受其他写入器的影响。如果是这种情况,您可能想要寻找一些现有的框架,这些框架可能比滚动您自己的框架更好地满足需求。此外,在这个用例中,您可能需要考虑在应用程序中管理您的范围,并且只mmap
创建一个大文件,但这种方法的成本/收益将取决于文件的粒度大小。
如果没有更多关于应用程序的上下文,很难说更多。
对于您的后续问题:@synchronized
可以用来实现这一点,但并非没有与上面发布的 GCD 方式相同的机制。这样做的原因是,通过身份(指针相等)而不是值相等(即)进行@synchronized(foo)
同步,因此和(用于引用文件的两个最明显的对象)具有值语义,使它们成为不好的候选者。using 的实现可能如下所示:foo
-isEqual:
NSString
NSURL
@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(或其他东西)获得它。总而言之,虽然你可以使用它@synchronized
来做到这一点,现在这不是一个好的选择。它比等效的 GCD 机制慢得多。这些天来,唯一@synchronized
有用的时间是作为实现递归锁定的语法快捷方式。也就是说,许多聪明人认为递归锁定是一种反模式。(有关原因的更多详细信息,请查看此链接。)总而言之,这@synchronized
不是解决此问题的最佳方法。