我正在用Objective-C编写一个下载管理器,它可以同时从多个段下载文件,以提高速度。文件的每个段都是以线程的形式下载的。
一开始,我想将每个片段写在不同的文件中,并在下载结束时将所有文件放在一起。但由于许多原因,这不是一个好的解决方案。
因此,我正在寻找一种在特定位置写入文件的方法,这种方法能够处理多线程,因为在我的应用程序中,每个段都是在线程中下载的。在Java语言中,我知道FileChannel
完美地完成了这项工作,但在Objective-C中我却一无所知。
发布于 2012-02-17 01:04:00
永远不要忘记,Obj-C基于普通的C,因此我只需要编写一个自己的类,它使用标准C API处理文件I/O,它允许您将当前写入位置放在新文件中的任何位置,甚至远远超出当前文件的大小(缺少的字节由零字节填充),还可以随意向前和向后跳转。实现线程安全的最简单方法是使用锁,这不是最快的方法,但在您的特定情况下,我打赌瓶颈肯定不是线程同步。这个类可以有一个像这样的头:
@interface MultiThreadFileWriter : NSObject
{
@private
FILE * i_outputFile;
NSLock * i_fileLock;
}
- (id)initWithOutputPath:(NSString *)aFilePath;
- (BOOL)writeBytes:(const void *)bytes ofLength:(size_t)length
toFileOffset:(off_t)offset;
- (BOOL)writeData:(NSData *)data toFileOffset:(off_t)offset;
- (void)close;
@end
和一个类似于下面的实现:
#import "MultiThreadFileWriter.h"
@implementation MultiThreadFileWriter
- (id)initWithOutputPath:(NSString *)aFilePath
{
self = [super init];
if (self) {
i_fileLock = [[NSLock alloc] init];
i_outputFile = fopen([aFilePath UTF8String], "w");
if (!i_outputFile || !i_fileLock) {
[self release];
self = nil;
}
}
return self;
}
- (void)dealloc
{
[self close];
[i_fileLock release];
[super dealloc];
}
- (BOOL)writeBytes:(const void *)bytes ofLength:(size_t)length
toFileOffset:(off_t)offset
{
BOOL success;
[i_fileLock lock];
success = i_outputFile != NULL
&& fseeko(i_outputFile, offset, SEEK_SET) == 0
&& fwrite(bytes, length, 1, i_outputFile) == 1;
[i_fileLock unlock];
return success;
}
- (BOOL)writeData:(NSData *)data toFileOffset:(off_t)offset
{
return [self writeBytes:[data bytes] ofLength:[data length]
toFileOffset:offset
];
}
- (void)close
{
[i_fileLock lock];
if (i_outputFile) {
fclose(i_outputFile);
i_outputFile = NULL;
}
[i_fileLock unlock];
}
@end
锁可以通过各种方式避免。使用中央分派和数据块来调度串行队列上的查找+写入操作将会起作用。另一种方法是使用UNIX ( POSIX )文件处理程序,而不是标准的C处理程序(open()
和int
,而不是FILE *
和fopen()
),多次复制处理程序(dup()
函数),然后将每个处理程序放在不同的文件偏移量上,这避免了在每次写入和锁定时进一步寻找操作,因为POSIX I/O是线程安全的。然而,这两种实现都会稍微复杂一些,可移植性较差,并且没有可测量的速度改进。
发布于 2012-02-17 01:49:26
到目前为止,给出的答案有一些明显的缺点:
使用系统调用的
一种线程安全、高效、无锁的方法是使用内存映射,其工作原理如下:
为read/write
mmap()
munmap()
close()
实际的编写是由内核处理的--您的程序永远不会发出任何形式的write
系统调用。内存映射通常没有什么缺点,被广泛用于共享库之类的东西。
更新:一段代码可以说超过1000个单词...这是基于锁的多线程文件写入器的mmap
版本。注意,写入被简化为一个简单的memcpy
,它不会失败(!!),因此没有要检查的BOOL success
。性能相当于基于锁的版本。(通过并行写入100个1mb块进行测试)
关于基于mmap
的方法的"overkill“的评论:这种方法使用更少的代码行,不需要锁定,在编写时不太可能阻塞,在编写时不需要检查返回值。唯一的“过度杀伤力”是,它要求开发人员理解另一个概念,而不是好的旧的读/写文件I/O。
直接读入mmapped内存区域的可能性被省略了,但实现起来相当简单。您可以直接在文件中使用read(fd,i_filedata+offset,length);
或recv(socket,i_filedata+offset,length,flags);
。
@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
发布于 2012-02-16 22:47:27
将段对象排入队列,因为它们被接收到写线程。写线程应该保留一个无序对象的列表,以便实际的磁盘写入是顺序的。如果一个片段下载失败,它可以被推回到下载线程池中进行另一次尝试(也许应该保留一个内部重试计数)。我建议使用段对象池来防止一个段的一个或多个下载失败,从而在下载后段并将其添加到列表中时导致失控的内存使用。
https://stackoverflow.com/questions/9319874
复制相似问题