使用GCD进行大型文件复制 - Dispatch IO会占用大量内存

时间:2014-06-09 04:39:08

标签: ios macos memory-leaks io grand-central-dispatch

我正在将大型文件复制操作从NSStream转换为使用GCD的调度IO实现。

将两个1GB文件一起复制到一个2GB文件时,该应用程序使用GCD消耗2GB内存。 NSStream实现只消耗50MB。

在Instruments中,我可以看到start_wqthread调用分配1MB块,正如我所请求的块大小用于调度IO高水位标记,但是在写入输出流后没有被释放,它们会挂起

如何在将缓冲区写入输出流后释放缓冲区?

如果我在Xcode中创建一个全新的OS X Cocoa应用程序并在applicationDidFinishLaunching:方法中粘贴以下代码,它将消耗500-2000MB的内存。 (要测试,请使用本地文件引用替换临时文件引用。)

使用面向OS 10.9的OS 10.9 SDK创建新项目时,ARC禁止调用dispatch_release()。在旧项目中定位OS 10.6时,即使启用了ARC,也允许调用dispatch_release(),但不会影响内存占用。

NSArray* files = @[@"/1GBFile.tmp", @"/1GBFile2.tmp"];
NSString* outFile = @"/outFile.tmp";
NSString* queueName = [NSString stringWithFormat:@"%@.IO", [[NSBundle mainBundle].infoDictionary objectForKey:(id)kCFBundleIdentifierKey]];

dispatch_queue_t queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
dispatch_io_t io_write = dispatch_io_create_with_path(DISPATCH_IO_STREAM, outFile.UTF8String, (O_RDWR | O_CREAT | O_APPEND), (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH), queue, NULL);
dispatch_io_set_high_water(io_write, 1024*1024);

[files enumerateObjectsUsingBlock:^(NSString* file, NSUInteger idx, BOOL *stop) {
    dispatch_io_t io_read = dispatch_io_create_with_path(DISPATCH_IO_STREAM, file.UTF8String, O_RDONLY, 0, queue, NULL);
    dispatch_io_set_high_water(io_read, 1024*1024);
    dispatch_io_read(io_read, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t data, int error) {
        if (error) {
            dispatch_io_close(io_write, 0);
            return;
        }

        if (data) {
            size_t bytesRead = dispatch_data_get_size(data);
            if (bytesRead > 0) {
                dispatch_io_write(io_write, 0, data, queue, ^(bool doneWriting, dispatch_data_t dataToBeWritten, int errorWriting) {
                    if (errorWriting) {
                        dispatch_io_close(io_read, DISPATCH_IO_STOP);
                    }
                });
            }
        }

        if (done) {
            dispatch_io_close(io_read, 0);
            if (files.count == (idx+1)) {
                dispatch_io_close(io_write, 0);
            }
        }
    });
}];

2 个答案:

答案 0 :(得分:2)

我相信我已经使用调度组制定了解决方案。

代码实质上是按顺序复制每个文件(阻止循环处理下一个文件,直到完全读取和写入前一个文件),但允许文件读写操作异步排队。

我认为内存过度消耗是因为多个文件的读取同时排队。我原本以为串口队列就好了,但它似乎阻止了调度组的进度,所以只有读取和写入单个文件的工作才会排队等待。使用以下代码,峰值内存使用量约为7MB。

现在,单个输入文件排队等待读取,每个读取操作将其相应的写入操作排队,并且阻止输入文件上的循环,直到所有读取和写入操作都完成。

NSArray* files = @[@"/1GBFile.tmp", @"/1GBFile2.tmp"];
NSString* outFile = @"/outFile.tmp";
NSString* queueName = [NSString stringWithFormat:@"%@.IO", [[NSBundle mainBundle].infoDictionary objectForKey:(id)kCFBundleIdentifierKey]];

dispatch_queue_t queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
dispatch_group_t group = dispatch_group_create();
dispatch_io_t io_write = dispatch_io_create_with_path(DISPATCH_IO_STREAM, outFile.UTF8String, (O_RDWR | O_CREAT | O_APPEND), (S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH), queue, NULL);
dispatch_io_set_high_water(io_write, 1024*1024);

[files enumerateObjectsUsingBlock:^(NSString* file, NSUInteger idx, BOOL *stop) {
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    if (*stop) {
        return;
    }
    dispatch_group_enter(group);
    dispatch_io_t io_read = dispatch_io_create_with_path(DISPATCH_IO_STREAM, file.UTF8String, O_RDONLY, 0, queue, NULL);
    dispatch_io_set_high_water(io_read, 1024*1024);
    dispatch_io_read(io_read, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t data, int error) {
        if (error || *stop) {
            dispatch_io_close(io_write, 0);
            *stop = YES;
            return;
        }

        if (data) {
            size_t bytesRead = dispatch_data_get_size(data);
            if (bytesRead > 0) {
                dispatch_group_enter(group);
                dispatch_io_write(io_write, 0, data, queue, ^(bool doneWriting, dispatch_data_t dataToBeWritten, int errorWriting) {
                    if (errorWriting || *stop) {
                        dispatch_io_close(io_read, DISPATCH_IO_STOP);
                        *stop = YES;
                        dispatch_group_leave(group);
                        return;
                    }

                    if (doneWriting) {
                        dispatch_group_leave(group);
                    }
                });
            }
        }

        if (done) {
            dispatch_io_close(io_read, 0);
            if (files.count == (idx+1)) {
                dispatch_io_close(io_write, 0);
            }
            dispatch_group_leave(group);
        }
    });
}];

答案 1 :(得分:1)

我不确定[self cleanUpAndComplete];是什么,但是,您似乎没有为您创建的其他频道拨打dispatch_close仅限 io_read)。

- 来自dispatch_create

  

返回的对象在返回之前保留;这是你的   有责任关闭频道,然后释放此对象   你已经完成了它的使用。