主队列上的dispatch_sync与dispatch_async

忍受着我,这是要解释一下。 我有一个如下所示的函数。

上下文:“aProject”是一个名为LPProject的核心数据实体,其中包含名为“memberFiles”的数组,其中包含另一个名为LPFile的Core Data实体的实例。 每个LPFile代表磁盘上的一个文件,我们想要做的就是打开这些文件并parsing它的文本,寻找指向OTHER文件的@import语句。 如果我们find@import语句,我们希望find它们指向的文件,然后通过向表示第一个文件的核心数据实体添加一个关系来将该文件“链接”到这个文件。 由于所有这些都可能需要一些时间在大文件上,我们将使用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上运行它,它将无法正常运行。 我们永远不会回到主线程, NSLog(@"got to main thread"); 从不出现,应用程序挂起。 但是,(这是真正奇怪的地方)—如果我在小项目FIRST上运行代码,然后在大项目上运行代码,那么所有的工作都是完美的。 只有当我在大型项目上运行代码时,才会出现问题。

如果我将第二个调度行更改为:

 dispatch_async(dispatch_get_main_queue(), ^{ 

(也就是说,使用async而不是sync来将块分派给主队列),一切都可以正常工作。 完美。 无论项目中有多less文件!

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

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

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

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

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

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);

并且在您的class级声明中添加这个作为ivar:

dispatch_queue_t taskQ;

我相信Ryan是在正确的道路上:当一个项目有1500个文件(我决定testing的数量)时,会产生太多的线程。

所以,我重构了上面的代码,像这样工作:

 - (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所build议的那样设置了一个“串行”队列(for循环是它的串行部分),但是有一个好处:当for循环结束时,我们完成了所有文件的处理, dispatch_async(main_queue)块在那里做任何我们想要的。 这是一个非常好的方式来告诉何时并发处理任务完成,并没有在我的旧版本中存在。

这里的缺点是在multithreading上使用Core Data要复杂一些。 但是这种方法似乎对5000个文件的项目(这是我testing过的最高的项目)是无懈可击的。

我认为用图表比较容易理解:

对于作者所描述的情况:

| TASKQ | ***********开始|

| dispatch_1 *********** | ———

| dispatch_2 ************* | ———

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

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

************************* | –dispatch_1– | –dispatch_2– | –dispatch3– | ****** *********************** | –dispatch_n |,

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