串行队列上的核心数据堆栈导致死锁

Posted

技术标签:

【中文标题】串行队列上的核心数据堆栈导致死锁【英文标题】:Core Data stack on serial queue results in deadlock 【发布时间】:2015-03-31 20:19:04 【问题描述】:

我正在尝试将核心数据数据库迁移到 Realm(大约在 0-2 百万行之间),并且遇到了一个死锁,据我所知,这是不应该发生的。

从一个单例类,我像这样启动迁移:

_queue = dispatch_queue_create("DiagnosticMigrationQueue", NULL);
    dispatch_async(_queue, ^
        _realmMigrator = [[CoreDataToRealmMigrator alloc] init];
        [_realmMigrator performMigrationToRealm];
    );

performMigrationToRealm 方法中,我这样设置了Core Data 堆栈:

- (void) performMigrationToRealm
    
    self.migrationIsRunning = YES;

    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"persistentStore"];

    NSError *error;

    NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    context.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    [context.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                 configuration:@"DiagnosticData"
                                                           URL:url
                                                       options:nil
                                                         error:&error];

了解检查:当我创建队列时,尚未设置任何 Core Data 堆栈。因此,NSManagedObjectContext 只在 GCD 决定放置我的块的任何线程上创建。

到目前为止,一切都很好。没问题。我现在运行一个方法 - 以 100,000 个批次 - 获取实体中的所有 NSManagedObjectIds。它看起来像这样:

for (NSInteger numberOfMigratedBatches = 0; numberOfMigratedBatches < totalNumberOfBatches; numberOfMigratedBatches++)

    NSArray *samples = [context executeFetchRequest:fetchRequest error:&error];
    if (samples && !error) 
    
        [self transferWeightSamplesToRealmWithObjectIds:samples withContext:context];
    
    [context reset];
    fetchRequest.fetchOffset = batchSize * (numberOfMigratedBatches+1);

上面代码块中的fetchRequest 是时髦的行。即使我已经在串行队列上启动了这个过程,我还是以某种方式结束了这个:

这些 minion_duties2 线程中的每一个都卡在同一行代码上,即上面的 fetchRequest

这里发生了什么?我了解队列!= 线程,并且 GDC 会将我的代码放在任何认为合适的线程上。但是,我不希望它会将我的代码放在三个线程上。另外,com.apple.root.user-initiated-qos.overcommit 是什么?我会说这是过度使用。我只希望这段代码运行一次!

【问题讨论】:

据我所知,您的代码看起来不错。如果我不得不猜测,我会说:这是几个不同的 fetchRequests 试图在几个不同的线程上执行可能导致死锁。您是否有任何理由手动进行批处理,而不是通过在 NSFetchRequest 上设置 fetchBatchSize 属性让 Core Data 为您完成? Emm.. 可能是我的疏忽。我之前没有做过很多需要批处理的 fetchRequest,所以我没有意识到 Core Data 可以为我完成。 【参考方案1】:

因此,从技术上讲,这不是对您发布的问题的回答,但它可能会为您提供一种避免遇到的问题的方法(解决方法?)。

由于我的猜测是尝试在几个不同的线程上执行导致死锁的几个不同的 fetch 请求,我建议不要手动进行批处理,而是通过在 NSFetchRequest 上设置 fetchBatchSize 属性来完成,然后让核心数据为您完成,因此避免完全执行多个获取请求,如果内存使用是一个问题,请尝试将您的 for 循环的内部包装在一个 autoreleasepool 块中(我猜transferWeightSamplesToRealmWithObjectIds:withContext: 中有一个 for 循环方法)。

核心数据有一个称为批处理和故障的概念。来自Apple documentation of NSFetchRequest:

如果您设置非零批量大小,则返回的对象集合 当 fetch 被执行时被分成多个批次。当提取是 执行,整个请求被评估和所有的身份 记录匹配对象,但不超过 batchSize 对象的数据 将一次从持久存储中获取。数组 从执行请求返回的将是一个代理对象 透明地根据需要对批次进行故障处理。 (在数据库术语中,这是一个 内存中的光标。)

希望这对您有所帮助。

【讨论】:

以上是关于串行队列上的核心数据堆栈导致死锁的主要内容,如果未能解决你的问题,请参考以下文章

ios多线程同步异步、串行并行队列、死锁

串行队列的死锁

Combine 的 receive(on:) 没有分派到串行队列,导致数据竞争

多线程处理怎么使用绿联

10.22进程互斥锁,队列,堆栈,线程

说说GCD中的死锁