52 votes

dispatch_sync vs dispatch_async sur la file d'attente principale

Ours avec moi, ça va prendre un peu difficile à expliquer. J'ai une fonction qui ressemble à celui ci-dessous.

Contexte: "unprojeter" est une Base de Données de l'entité nommée LPProject avec un tableau nommé "memberFiles' qui contient des instances d'une autre Base de Données de l'entité appelée LPFile. Chaque LPFile représente un fichier sur le disque et ce que nous voulons faire est d'ouvrir chacun de ces fichiers et de les analyser son texte, à la recherche de @déclarations d'importation qui pointent vers d'AUTRES fichiers. Si nous trouvons @déclarations d'importation, nous voulons localiser le fichier qu'ils indiquent et puis "lien" qui fichier à celle-ci par l'ajout d'une relation de la base de données de l'entité qui représente le premier fichier. Depuis tout cela peut prendre un certain temps sur de gros fichiers, nous allons le faire à la sortie de l'thread à l'aide de PGCD.

- (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
        }
    }
}

Maintenant, voici où les choses deviennent étranges:

Ce code fonctionne, mais je vais voir un étrange problème. Si je l'exécute sur un LPProject qui a quelques fichiers (environ 20), il fonctionne parfaitement. Cependant, si je le lance sur un LPProject qui a plus de fichiers (par exemple, de 60 à 70), elle ne peut PAS s'exécuter correctement. Nous n'avons jamais revenir au thread principal, l' NSLog(@"got to main thread"); n'apparaît jamais, et l'application se bloque. MAIS, (et c'est là que les choses deviennent VRAIMENT bizarre) --- si j'exécute le code sur le petit projet d'ABORD et ENSUITE l'exécuter sur le grand projet, tout fonctionne parfaitement. C'est SEULEMENT lorsque j'exécute le code sur le grand projet d'abord que le problème se présente.

Et voici le kicker, si je change la deuxième expédition de la ligne:

dispatch_async(dispatch_get_main_queue(), ^{

(C'est à dire d'utiliser async au lieu de sync de l'envoi de ce bloc à la file d'attente principale), tout fonctionne tout le temps. Parfaitement. Quel que soit le nombre de fichiers dans un projet!

Je suis à une perte pour expliquer ce comportement. Toute aide ou des conseils sur ce qu'à tester la prochaine serait appréciée.

53voto

Ryan Points 8958

Il s'agit d'un problème lié à disque I/O et PGCD. Fondamentalement, le PGCD est probablement la fraie un thread pour chaque fichier, et à un certain point, vous avez trop de threads pour le système de service dans un laps de temps raisonnable.

Chaque fois que vous appelez dispatch_async() et dans ce bloc vous tentative pour toutes les I/O (par exemple, on dirait que vous êtes la lecture de certains fichiers), il est probable que le thread dans lequel le bloc de code est en cours d'exécution va bloquer (get mis en pause par l'OS) pendant qu'il attend pour la lecture des données depuis le système de fichiers. La façon dont le PGCD œuvres est telle que lorsqu'il voit que l'un de ses threads de travail est bloqué sur les I/O et vous êtes encore en lui demandant de ne plus travailler simultanément, il va tout simplement lancer un nouveau thread de travail. Ainsi, si vous essayez d'ouvrir 50 fichiers sur un concurrent à la file d'attente, il est probable que vous allez finir par causer des PGCD de frayer ~50 threads.

C'est trop de threads pour que le système de manière significative service, et vous finirez par mourir de faim votre thread principal pour le CPU.

Le moyen de résoudre ce problème est d'utiliser une série de file d'attente au lieu d'un concurrent à la file d'attente pour faire de votre fichier. C'est facile à faire. Vous aurez envie de créer une série de file d'attente et l'enregistrer comme une ivar dans votre objet afin de ne pas créer plusieurs série des files d'attente. Donc, supprimer cet appel:

dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Ajouter ceci dans votre méthode init:

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

Ajouter ceci dans votre méthode dealloc:

dispatch_release(taskQ);

Et ajouter ce que l'un d'ivar dans votre déclaration de classe:

dispatch_queue_t taskQ;

5voto

Bryan Points 1305

Je crois que Ryan est sur le droit chemin: il y a tout simplement trop de threads d'être pondus quand un projet est de 1 500 dossiers (le montant que j'ai décidé de le tester.)

Donc, j'ai refait le code ci-dessus fonctionne comme ceci:

- (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
    }

Donc, fondamentalement, nous sommes maintenant fraie un thread qui lit tous les fichiers au lieu d'un-fil-par-fichier. Aussi, il s'avère que l'appel dispatch_async() sur le main_queue est la bonne approche: le thread de travail sera de l'expédition et de bloquer le thread principal et de ne PAS attendre qu'il revienne avant de procéder à l'analyse du fichier suivant.

Cette mise en œuvre essentiellement met en place une "série" de la file d'attente en tant que Ryan a suggéré (la boucle for est la série en fait partie), mais avec un avantage: lorsque la boucle se termine, nous avons terminé le traitement de tous les fichiers et nous pouvons juste coller un dispatch_async(main_queue) bloc là pour faire ce que nous voulons. C'est une très belle façon de dire quand le traitement simultané de la tâche est terminée et qu'il n'existait pas dans mon ancienne version.

L'inconvénient ici est que c'est un peu plus compliqué de travailler avec la Base de Données sur plusieurs threads. Mais cette approche semble être à l'épreuve des balles pour les projets de 5 000 fichiers (ce qui est le plus haut que j'ai testé.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X