4

我有一个使用 Grand Central 调度队列的工作实现,它(1)打开一个文件并计算“queue1”上的 OpenSSL DSA 哈希,(2)将哈希写到一个新的“side car”文件中,以便以后在“queue2”上验证.

我想同时打开多个文件,但基于一些不会通过打开 100 个文件并超过硬盘驱动器的可持续输出来“阻塞”操作系统的逻辑。iPhoto 或 Aperture 等照片浏览应用程序似乎可以打开多个文件并显示它们,所以我假设可以做到这一点。

我假设最大的限制将是磁盘 I/O,因为应用程序可以(理论上)同时读取和写入多个文件。

有什么建议么?

TIA

4

5 回答 5

7

你是对的,你肯定会受到 I/O 的限制。并且同时打开多个文件并被主动读取的随机访问性质会加剧这种情况。

因此,您需要取得一些平衡。正如您所观察到的,一个文件很可能不是最有效的。

亲自?

我会使用调度信号量。

就像是:

@property(nonatomic, assign) dispatch_queue_t dataQueue;
@property(nonatomic, assign) dispatch_semaphore_t execSemaphore;

和:

- (void) process:(NSData *)d {
    dispatch_async(self.dataQueue, ^{
        if (!dispatch_semaphore_wait(self.execSemaphore, DISPATCH_TIME_FOREVER)) {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                ... do calcualtion work here on d ...
                dispatch_async(dispatch_get_main_queue(), ^{
                    .... update main thread w/new data here ....
                });
                dispatch_semaphore_signal(self.execSemaphore);
            });
        }
    });
}

开始的地方:

self.dataQueue = dispatch_queue_create("com.yourcompany.dataqueue", NULL);
self.execSemaphore = dispatch_semaphore_create(3);
[self process: ...];
[self process: ...];
[self process: ...];
[self process: ...];
[self process: ...];
.... etc ....

您需要确定如何最好地处理排队。如果有很多项目并且有取消的概念,那么将所有内容都排入队列可能是一种浪费。同样,您可能希望将要处理的文件的 URL 排入队列,而不是像上面这样的 NSData 对象。

无论如何,上面将同时处理三件事,不管有多少已经入队。

于 2010-12-26T19:28:17.657 回答
6

您已经收到了很好的答案,但我想补充几点。我从事的项目会枚举文件系统中的所有文件并计算每个文件的 MD5 和 SHA1 哈希(除了其他处理)。如果您正在做类似的事情,您正在搜索大量文件并且文件可能包含任意内容,那么需要考虑以下几点:

  • 如前所述,您将受到 I/O 限制。如果同时读取多个文件,将对每个计算的性能产生负面影响。显然,并行调度计算的目标是使磁盘在文件之间保持忙碌,但您可能需要考虑以不同的方式组织工作。例如,设置一个线程来枚举和打开文件,第二个线程从第一个线程一次获取打开的文件句柄并处理它们。文件系统会缓存目录信息,因此枚举不会对读取数据产生严重影响,而读取数据实际上是要打到磁盘上的。

  • 如果文件可以任意大,Chris 的方法可能不实用,因为整个内容都被读入内存。

  • 如果除了计算哈希之外您对数据没有其他用途,那么我建议在读取数据之前禁用文件系统缓存。

如果使用 NSFileHandles,一个简单的类别方法将针对每个文件执行此操作:

@interface NSFileHandle (NSFileHandleCaching)
- (BOOL)disableFileSystemCache;
@end

#include <fcntl.h>

@implementation NSFileHandle (NSFileHandleCaching)
- (BOOL)disableFileSystemCache {
     return (fcntl([self fileDescriptor], F_NOCACHE, 1) != -1);
}
@end
  • 如果 sidecar 文件很小,您可能希望将它们收集到内存中并分批写入,以最大程度地减少处理中断。

  • 文件系统(至少是HFS)按顺序存储目录中文件的文件记录,因此遍历文件系统广度优先(即在进入子目录之前处理目录中的每个文件)。

当然,以上只是建议。您将需要试验和测量性能以确认实际影响。

于 2010-12-27T11:06:23.280 回答
6

我会为此使用 NSOperation ,因为它可以轻松处理依赖项和取消。

我将为读取数据文件、计算数据文件的哈希和写入边车文件创建一个操作。我会让每个写操作都依赖于其关联的计算操作,并且每个计算操作都依赖于其关联的读操作。

然后我会将读写操作添加到一个 NSOperationQueue,即“I/O 队列”,宽度受限。我将添加到单独的 NSOperationQueue 的计算操作,即“计算队列”,其宽度不受限制。

I/O 队列宽度受限的原因是您的工作可能会受到 I/O 限制;您可能希望它的宽度大于 1,但它很可能与输入文件所在的物理磁盘的数量直接相关。(可能类似于 2x,您需要通过实验来确定。)

代码最终看起来像这样:

@implementation FileProcessor

static NSOperationQueue *FileProcessorIOQueue = nil;
static NSOperationQueue *FileProcessorComputeQueue = nil;

+ (void)inititalize
{
    if (self == [FileProcessor class]) {
        FileProcessorIOQueue = [[NSOperationQueue alloc] init];
        [FileProcessorIOQueue setName:@"FileProcessorIOQueue"];
        [FileProcessorIOQueue setMaxConcurrentOperationCount:2]; // limit width

        FileProcessorComputeQueue = [[NSOperationQueue alloc] init];
        [FileProcessorComputeQueue setName:@"FileProcessorComputeQueue"];
    }
}

- (void)processFilesAtURLs:(NSArray *)URLs
{
    for (NSURL *URL in URLs) {
        __block NSData *fileData = nil; // set by readOperation
        __block NSData *fileHashData = nil; // set by computeOperation

        // Create operations to do the work for this URL

        NSBlockOperation *readOperation =
            [NSBlockOperation blockOperationWithBlock:^{
                fileData = CreateDataFromFileAtURL(URL);
            }];

        NSBlockOperation *computeOperation =
            [NSBlockOperation blockOperationWithBlock:^{
                fileHashData = CreateHashFromData(fileData);
                [fileData release]; // created in readOperation
            }];

        NSBlockOperation *writeOperation =
            [NSBlockOperation blockOperationWithBlock:^{
                WriteHashSidecarForFileAtURL(fileHashData, URL);
                [fileHashData release]; // created in computeOperation
            }];

        // Set up dependencies between operations

        [computeOperation addDependency:readOperation];
        [writeOperation addDependency:computeOperation];

        // Add operations to appropriate queues

        [FileProcessorIOQueue addOperation:readOperation];
        [FileProcessorComputeQueue addOperation:computeOperation];
        [FileProcessorIOQueue addOperation:writeOperation];
    }
}

@end

这很简单;dispatch_*NSOperation 允许您独立定义工作单元和它们之间的依赖关系,而不是像处理 API 那样处理多重嵌套的同步/异步层。在某些情况下,这可能更容易理解和调试。

于 2010-12-26T20:33:03.533 回答
2

libdispatch 实际上为此明确提供了 API!查看 dispatch_io;它将在适当的时候处理并行化 IO,否则将其序列化以避免磁盘抖动。

于 2012-09-16T22:31:33.360 回答
1

以下链接指向我使用 NSOperation 和 Grand Central Dispatch 设置的 BitBucket 项目,该项目使用原始文件完整性应用程序。

https://bitbucket.org/torresj/hashar-cocoa

我希望它有帮助/使用。

于 2012-09-02T23:27:13.157 回答