1

特定のリソースをロックするために使用することは知ってdispatch_barrier_asyncいますが、私の場合は、ディスク上のリソースではなく共有データ構造を変更しておらず、キュー全体をブロックしたくないため、良い候補ではありません。アクションには時間がかかる可能性があるため、指定されたキー。複数のスレッドから同時に同じファイルに (名前で) アクセスすることに関して、ファイル システムがどのように機能するかはわかりません。ドキュメントで明確な答えを見つけることができませんでした。「ファイル名」でロックしたいのですが、メソッド「tryLock(key)」がありません

何かのようなもの:

-(void)readFileAtPath:(NSString *)path completion:(void(^)(NSData *fileData))completion
{
   dispatch_async(self.concurrentQueue,^{
       // acquire the lock for a given key and block until can acquire
       trylock(path);
       NSData *fileData = [self dataAtPath:path];
       unlock(path);
       completion(fileData);
   });
}

-(void)writeData:(NSData *)data toPath:(NSString *)path completion:(void(^)())completion
{
    dispatch_async(self.concurrentQueue,^{
        // if someone is reading the data at 'path' then this should wait - otherwise should write
        trylock(path);
        [data writeToFile:path atomically:YES];
        unlock(path);
        completion();
    });
}

編集:

@synchronizedこれを行いますか?これは適切なユースケースですか?

4

1 に答える 1

2

「スコープ付きキュー」を作成したい場合は、それを実行してください。ファイルごとにシリアル キューを作成し、コンカレント キューをターゲットにします。次のようになります。

@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 パフォーマンスの向上を目的としている場合は、少し「コードの匂い」があります。頭のてっぺんから、最大のパフォーマンスを得るには、ファイルごとのキューよりも物理デバイスごとにキューを作成する方が良いと感じています。一般に、開発者がファイル システム アクセスを調整する方法を OS/システム フレームワークよりもよく知っているとは限らないため、このアプローチが実際にパフォーマンスを向上させていることを確認するために、事前と事後を測定する必要があります。確かに、OS が知らないことを知っている場合もありますが、車輪を再発明するのではなく、OS にその情報を提供する方法を探したいと思うかもしれません。読み取りと書き込みのパフォーマンスに関して、使用する場合dispatch_ioファイルを読み書きするためのチャネルを作成すると、ファイル アクセスを最適に調整するために必要な情報を GCD に提供することになります。

また、「アプリケーションをそれ自体から保護」しようとしている可能性もあると思います。同様に、複数のタスクが同時にファイルにアクセスする可能性があるキャッシュとしてディスクを使用している場合、リーダーを別のライターから保護する必要があるかもしれません。このような場合は、独自のフレームワークを作成するよりも、そのニーズに適切に対応できる既存のフレームワークを探すことをお勧めします。また、このユースケースでは、スコープをアプリケーション内で管理し、mmap1 つの大きなファイルだけを ing することを検討することもできますが、このアプローチの費用対効果は、ファイルの粒度によって異なります。

アプリケーションに関するコンテキストがなければ、これ以上言うのは難しいでしょう。

フォローオンの質問:これを達成するために使用@synchronized できますが、GCD の方法で上記に投稿したのと同じメカニズムが必要です。この理由は、値の等価性(すなわち) ではなくID (ポインターの等価性)によって@synchronized(foo)同期するため、および(ファイルを参照するために使用される最も明白な 2 つのオブジェクト) が値のセマンティクスを持つため、それらは候補として不適切になります。を使用した実装は次のようになります。foo-isEqual:NSStringNSURL@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

同期と非同期の 2 つのメソッドを作成したことがわかります。@synchronized相互排除メカニズムを提供しますが、非同期ディスパッチメカニズムではないため、並列処理が必要な場合でも、GCD (または他の何か) から取得する必要があります@synchronized。最近では良い選択肢ではありません。同等の GCD メカニズムよりもかなり遅いです。@synchronized最近では、再帰ロックを実現するための構文上のショートカットとしてのみ有用です。とはいえ、多くの賢明な人々は、再帰的ロックはアンチパターンであると信じています。(理由の詳細については、このリンクを参照してください。) 一長一短は@synchronized、この問題を解決する最善の方法ではないということです。

于 2014-08-20T11:28:32.250 に答える