52

忍受着我,这是要做一些解释。我有一个如下所示的函数。dispatch_sync与主队列上的dispatch_async

上下文:“aProject”是名为LPProject的核心数据实体,其数组名为“memberFiles”,其中包含另一个名为LPFile的Core Data实体的实例。每个LPFile代表磁盘上的一个文件,我们想要做的就是打开这些文件并解析它的文本,寻找指向OTHER文件的@import语句。如果我们找到@import语句,我们希望找到它们指向的文件,然后通过向表示第一个文件的核心数据实体添加关系,将该文件“链接”到该文件。由于所有这些都可能需要一些时间处理大文件,我们将使用GCD从主线程中删除它。

- (void) establishImportLinksForFilesInProject:(LPProject *)aProject { 
    dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    for (LPFile *fileToCheck in aProject.memberFiles) { 
     if (//Some condition is met) { 
      dispatch_async(taskQ, ^{ 
       // Here, we do the scanning for @import statements. 
       // When we find a valid one, we put the whole path to the imported file into an array called 'verifiedImports'. 

       // go back to the main thread and update the model (Core Data is not thread-safe.) 
       dispatch_sync(dispatch_get_main_queue(), ^{ 

        NSLog(@"Got to main thread."); 

        for (NSString *import in verifiedImports) { 
          // Add the relationship to Core Data LPFile entity. 
        } 
       });//end block 
      });//end block 
     } 
    } 
} 

现在,这里的事情变得怪异:

此代码的工作,但我看到一个奇怪的问题。如果我在有几个文件(大约20个)的LPProject上运行它,它可以完美运行。但是,如果我在具有更多文件(例如60-70)的LPProject上运行它,它确实运行正确NOT。我们永远不会回到主线程,NSLog(@"got to main thread");从不出现,应用程序挂起。但是,(这是真正奇怪的地方)---如果我在小型项目FIRST上运行代码然后在大型项目上运行代码,那么一切都很完美。只有当我在大型项目上运行代码时,才会出现问题。

而这里的踢球,如果我改变第二派遣线到这一点:

dispatch_async(dispatch_get_main_queue(), ^{ 

(也就是说,使用async代替sync到块分派到主队列),一切正常,所有的时间。完美。无论项目中有多少文件!

我无法解释这种行为。任何帮助或提示下一步测试将不胜感激。

+0

注意:为了简洁起见,我已经编写了“扫描”和“核心数据条目”代码片段。但是,我几乎肯定他们不是罪魁祸首,因为如果我将所有内容放在单个线程中,并且他们在上述多线程情况下完美工作(通过首先运行一个小型项目“预热”所有内容和/或在主队列上使用dispatch_async()而不是dispatch_sync())。 – Bryan

+1

听起来就像是碰到了一个死锁问题 –

+0

当你处于这种状态时,你应该对你的应用程序运行示例或工具来查看其他线程都在做什么。如果他们陷入僵局,发生的事情应该更加明显。 –

回答

53

这是与磁盘I/O和GCD相关的常见问题。基本上,GCD可能会为每个文件产生一个线程,并且在某个时刻,您有太多的线程可以让系统在合理的时间内进行服务。

每次调用dispatch_async()并在该块中试图执行任何I/O(例如,看起来您正在读取某些文件)时,可能是该代码块中的线程正在执行将等待数据从文件系统中读取时将阻塞(由操作系统暂停)。 GCD的工作方式是当它看到它的一个工作线程在I/O上被阻塞,并且你仍然要求它同时做更多的工作时,它会产生一个新的工作线程。因此,如果您尝试在并发队列中打开50个文件,则最终可能会导致GCD产生大约50个线程。

这对于系统来说意味着服务的线程太多,并且最终导致CPU的主线程挨饿。

解决此问题的方法是使用串行队列而不是并发队列来执行基于文件的操作。这很容易做到。您需要创建一个串行队列并将其作为ivar存储在您的对象中,以免最终创建多个串行队列。所以删除此电话:

dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

在你的init方法补充一点:

taskQ = dispatch_queue_create("com.yourcompany.yourMeaningfulLabel", DISPATCH_QUEUE_SERIAL);

在你的dealloc方法补充一点:

dispatch_release(taskQ);

并把这个作为在你的班级声明中有一个ivar:

dispatch_queue_t taskQ;

+6

迈克阿什也有一个关于这个问题的优秀写作:http://mikeash.com/pyblog/friday-qa-2009-09-25-gcd-practicum.html –

+2

@Ryan - 感谢您的意见。这也发生在我身上,但如果问题太多并发线程,我们会期望大型项目每次都会失败。在这种情况下,只要我先在一个较小的项目上运行代码,它就可以工作。 (请注意,这两个项目是完全独立的文件,因此没有文件被缓存等) – Bryan

+1

如果启用了自动引用计数,该怎么办? – byJeevan

5

我认为瑞安是在正确的道路上:有太多的线程时一个项目有1500个文件正在催生

所以,我重构了(我决定测试量。)上面的代码工作是这样的:

- (void) establishImportLinksForFilesInProject:(LPProject *)aProject 
{ 
     dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

    dispatch_async(taskQ, 
    ^{ 

    // Create a new Core Data Context on this thread using the same persistent data store  
    // as the main thread. Pass the objectID of aProject to access the managedObject 
    // for that project on this thread's context: 

    NSManagedObjectID *projectID = [aProject objectID]; 

    for (LPFile *fileToCheck in [backgroundContext objectWithID:projectID] memberFiles]) 
    { 
     if (//Some condition is met) 
     { 
       // Here, we do the scanning for @import statements. 
       // When we find a valid one, we put the whole path to the 
       // imported file into an array called 'verifiedImports'. 

       // Pass this ID to main thread in dispatch call below to access the same 
       // file in the main thread's context 
       NSManagedObjectID *fileID = [fileToCheck objectID]; 


       // go back to the main thread and update the model 
       // (Core Data is not thread-safe.) 
       dispatch_async(dispatch_get_main_queue(), 
       ^{ 
        for (NSString *import in verifiedImports) 
        { 
         LPFile *targetFile = [mainContext objectWithID:fileID]; 
         // Add the relationship to targetFile. 
        } 
       });//end block 
     } 
    } 
    // Easy way to tell when we're done processing all files. 
    // Could add a dispatch_async(main_queue) call here to do something like UI updates, etc 

    });//end block 
    } 

所以,基本上,我们现在产卵一个线程读取所有的文件,而不是一个线程每个文件。另外,事实证明,在main_queue上调用dispatch_async()是正确的方法:工作线程会将该块分派给主线程,并且在继续扫描下一个文件之前不等待它返回。

这个实现基本上建立了一个Ryan建议的“串行”队列(for循环是它的串行部分),但有一个优点:当for循环结束时,我们完成了所有文件的处理,我们可以在那里粘贴一个dispatch_async(main_queue)块来做我们想做的任何事情。这是一个非常好的方式,可以告诉我什么时候并发处理任务已完成,并且在旧版本中不存在。

这里的缺点是在多线程上使用Core Data要复杂一点。但是,这种方法似乎是防弹的项目与5000页的文件(这是我测试过的最高水平。)

+1

是的,你原来的问题并没有说明你的“文件”真的是CoreData对象。这是一个完全不同的问题。我的回复涉及到实际的文件I/O。在这一点上,我意识到如果没有看到完整的源代码列表,我实际上无法知道你在做什么。我不确定何时进行文件I/O或从CoreData中读取数据。如果您需要更多输入信息,请随时列出您正在做的事情的来源。 – Ryan

0

我觉得它更容易与图就明白了:

对于笔者所描述的情况:

| taskQ | ***********开始|

| dispatch_1 *********** | ---------

| dispatch_2 ************* | --- ------

| dispatch_n *************************** | ----------

| main队列(同步)| **开始派送到main |

************************* | --dispatch_1-- | --dispatch_2-- | --dispatch3-- | ** *************************** | --dispatch_n |,

这使得同步主队列如此繁忙以致最终导致任务失败。

相关问题