I'm writing a download manager in Objective-C which downloads file from multiple segments at the same times in order to improve the speed. Each segement of the file is downloaded in a thread.
At first, I thought to write each segment in a different file and to put together all the files at the end of the download. But for many reasons, it's not a good solution.
So, I'm searching a way to write in a file at a specific position and which is able to handle multiple thread because in my application, each segment is downloaded inside a thread.
In Java, I know that FileChannel
does the trick perfectly but I have no idea in Objective-C.
The answers given thus far have some clear disadvantages:
A thread safe, efficient, lock free approach would be to use memory mapping, which works as follows:
open()
the file for read/writemmap()
it to some place in memory. The file now "lives" in memory.munmap()
the memory and close()
the fileThe actual writing is handled by the kernel - your program will never issue a write
system call of any form. Memory mapping generally has little downsides and is used extensively for things like shared libraries.
update: a piece of code says more than 1000 words... This is the mmap
version of Mecki's lock-based multi-thread file writer. Note that writing is reduced to a simple memcpy
, which cannot fail(!!), so there is no BOOL success
to check. Performance is equivalent to the lock based version. (tested by writing 100 1mb blocks in parallel)
Regarding a comment on "overkill" of an mmap
based approach: this uses less lines of code, doesn't require locking, is less likely to block on writing, requires no checking of return values on writing. The only "overkill" would be that it requires the developer to understand another concept than good old read/write file I/O.
The possibility to read directly into the mmapped memory region is left out, but is quite simple to implement. You can just read(fd,i_filedata+offset,length);
or recv(socket,i_filedata+offset,length,flags);
directly into the file.
@interface MultiThreadFileWriterMMap : NSObject
{
@private
FILE * i_outputFile;
NSUInteger i_length;
unsigned char *i_filedata;
}
- (id)initWithOutputPath:(NSString *)aFilePath length:(NSUInteger)length;
- (void)writeBytes:(const void *)bytes ofLength:(size_t)length
toFileOffset:(off_t)offset;
- (void)writeData:(NSData *)data toFileOffset:(off_t)offset;
- (void)close;
@end
#import "MultiThreadFileWriterMMap.h"
#import <sys/mman.h>
#import <sys/types.h>
@implementation MultiThreadFileWriterMMap
- (id)initWithOutputPath:(NSString *)aFilePath length:(NSUInteger)length
{
self = [super init];
if (self) {
i_outputFile = fopen([aFilePath UTF8String], "w+");
i_length = length;
if ( i_outputFile ) {
ftruncate(fileno(i_outputFile), i_length);
i_filedata = mmap(NULL,i_length,PROT_WRITE,MAP_SHARED,fileno(i_outputFile),0);
if ( i_filedata == MAP_FAILED ) perror("mmap");
}
if ( !i_outputFile || i_filedata==MAP_FAILED ) {
[self release];
self = nil;
}
}
return self;
}
- (void)dealloc
{
[self close];
[super dealloc];
}
- (void)writeBytes:(const void *)bytes ofLength:(size_t)length
toFileOffset:(off_t)offset
{
memcpy(i_filedata+offset,bytes,length);
}
- (void)writeData:(NSData *)data toFileOffset:(off_t)offset
{
memcpy(i_filedata+offset,[data bytes],[data length]);
}
- (void)close
{
munmap(i_filedata,i_length);
i_filedata = NULL;
fclose(i_outputFile);
i_outputFile = NULL;
}
@end
Queue up the segment-objects as they are received to a writer-thread. The writer-thread should keep a list of out-of-order objects so that the actual disk-writing is sequential. If a segment download fails, it can be pushed back onto the downloading thread pool for another try, (perhaps an internal retry-count should be kept). I suggest a pool of segment-objects to prevent one or more failed download of one segment resulting in runaway memory use as later segments are downloaded and added to the list.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With