Multithreading et parallélisme dans mon application iOS en Swift

JérémyJérémy Membre
mars 2016 modifié dans Objective-C, Swift, C, C++ #1

Bonjour à  tous,


 


Dans l'application que je suis en train de développer, j'aimerais pouvoir exécuter de façon simultanée deux blocs de code. Pour information, les deux bloc ne sont pas dépendant l'un de l'autre. Voici la façon dont je m'y suis pris :

 



func myFunc() {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
aBloc()
}

dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
bBloc()
}
}


Quand je vais des tests via le simulateur, je n'ai pas l'impression que les deux algos s'exécutent en même temps. Mon impression est elle bonne ? Ceci est il normal ? Si oui, que dois-je faire ?


 


Merci pour vos réponses !  :D


Mots clés:
«1

Réponses

  • PyrohPyroh Membre

    Disons que les blocks vont être exécutés de manière concurrente mais dans leur ordre d'arrivée.


    Ici on ajoute aBloc → on commence l'exec. Dans le même temps bBloc est ajouté → on commence l'exec.


     


    Il est possible que le début de bBloc arrive après la fin de aBloc. Dans ce cas tu as l'impression que ça n'est pas concurrent. 


     


    Si les 2 blocs sont assez longs à  exécuter tu devrais voir une concurrence.


     


    Si tu nous en dit plus sur ces blocs et ce qu'ils sont censés faire on va peut-être pouvoir mieux t'aider.


  • Merci pour ta réponse Pyroh !  ;)


     


    Je vais rentrer un peu plus en détail. Je fais des calcul scientifiques (dans le domaine de l'astronomie) qui doivent être effectués de façon régulière (au moins toute les secondes). Pour réduire le temps de réponse d'affichage de tous les résultats, je me suis dit qu'il serait mieux de paralléliser les calculs.


     


    Exemple, dans le cas du calcul de la position en temps réel de la planète mercure (à  l'instant T) j'aimerai lancer aussi en simultanée l'algorithme qui calculera l'heure et lever et un troisième algorithme pour l'heure du coucher (jour le jour courant). Chaque algorithme n'a pas besoin de la réponse des autres pour pouvoir être exécuté. Il y a des calculs plus ou moins long. Mais pour que je puisse actualiser les données en temps réel, j'aimerais réduire au maximum le temps de rafraà®chissement de l'affichage des valeurs.  ::)


     


    Je ne sais pas si j'ai été très clair mais si tu as d'autres questions pour que tu aies plus de précision, n'hésite pas ! Merci encore ! 


  • PyrohPyroh Membre

    Ta réponse est claire rassure-toi.


    Maintenant l'idéal serait d'arrêter d'avoir des impressions et en bon scientifique que tu es et se reposer sur une méthode un peu plus empirique.


     


    Lance tes 3 algos comme tu le fais jusque là  mais au début de chacun tu mets un print("start algo x") et à  la fin print("end algo x"). Ensuite tu regarde le résultat dans la console et tu pourras déduire de toi meÌ‚me si l'exécution est concurrente ou séquentielle.


     


    Attention que tout ce qui est logs (NSLog, print, ...) et ce qui est mà j d'UI doivent se faire sur la main queue :



    dispatch_async(dispatch_get_main_queue()) {
    // Ton log ou màj UI
    }
  • Merci Pyroh !  :)


     


    Justement, qu'elle est la différence d'un point de vue exécution entre un :



    dispatch_get_main_queue()

    et un 



    dispatch_get_global_queue

    ?


  • MalaMala Membre, Modérateur

    dispatch_get_main_queue() renvoie une queue qui s'exécute dans le thread principal de l'appli. Dans ton cas, étant donné que tu veux mettre en concurrence tes calculs, il te faut taper dispatch_get_global_queue().


     


    Un peu de lecture sur GCD...


    https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/


     


  • PyrohPyroh Membre

    La main queue ne doit être utilisée dans ton cas que pour mettre à  jour l'UI UI ou logger dans la console. Sinon comme Mala le dit il faut utiliser dispatch_get_global_queue(). (Mala il est fort pour le multi-threading)


  • Okay, merci à  vous deux !  ;) 


    Petite dernière question. Comment faut il faire pour pourvoir exécuter en parallèle deux algorithmes, récupérer les deux valeurs pour pouvoir les additionner et appliquer un calcul sur la somme.


     


    Code (faux) qui illustre mes propos :



    func myFunc() {
    dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
    let a: Double = aBloc()
    }

    dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
    let b: Double = bBloc()
    }

    let c: Double = a + b
    myAlgo(c)
    }
  • Tu peux faire avec les NSOperation.


     



    NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
    myQueue.maxConcurrentOperationCount = 2;

    __block int firstResult;
    NSOperation *firstOperation = [NSBlockOperation blockOperationWithBlock:^{
    firstResult = 1;
    }];

    __block int secondResult;
    NSOperation *secondOperation = [NSBlockOperation blockOperationWithBlock:^{
    secondResult = 2;
    }];

    NSOperation *finalOperation = [NSBlockOperation blockOperationWithBlock:^{
    int c = firstResult + secondResult;
    }];

    [finalOperation addDependency:firstOperation];
    [finalOperation addDependency:secondOperation];

    [myQueue addOperation:firstOperation];
    [myQueue addOperation:secondOperation];
    [myQueue addOperation:finalOperation];
  • MalaMala Membre, Modérateur
    mars 2016 modifié #10

    Et sinon, il y a aussi les dispatch_group que j'aime bien.


     


    On créer un groupe: dispatch_group_t group = dispatch_group_create()


    On lance les blocs:   dispatch_group_async()


    On attend la fin d'exécution: dispatch_group_wait(group, DISPATCH_TIME_FOREVER)


     


    A voir ici pour culture...


    http://amro.co/gcd-using-dispatch-groups-for-fun-and-profit


     


    Edit: à  voir pour la version Swift mais cela va être dans le même esprit.


  • Mala, peux-tu nous mettre un schéma/exemple rapide avec les dispatch group ?


  • Merci colas !  8--) 


    J'imagine qu'il suffise que je transpose ton exemple en Swift et le tour sera joué.


     


    Merci bien ! 


  • colas_colas_ Membre
    mars 2016 modifié #13

    Oui, mais je ne swifte pas. Je ne sais pas si la balise __block existe en swift.


    Ici elle est importante.


  • Sinon en Swift ça donne ça :



    let myQueue = NSOperationQueue()
    myQueue.maxConcurrentOperationCount = 2
    var firstResult: Int = 0
    let firstOperation = NSBlockOperation(block: {
    firstResult = 1
    })
    var secondResult: Int = 0
    let secondOperation = NSBlockOperation(block: {
    secondResult = 2
    })
    let finalOperation = NSBlockOperation(block: {
    let _ = firstResult + secondResult
    })

    finalOperation.addDependency(firstOperation)
    finalOperation.addDependency(secondOperation)

    myQueue.addOperation(firstOperation)
    myQueue.addOperation(secondOperation)
    myQueue.addOperation(finalOperation)
  • JérémyJérémy Membre
    mars 2016 modifié #15

    Merci pour le tuyau Pyro ! 




  •  


    Sinon en Swift ça donne ça :



    let myQueue = NSOperationQueue()
    myQueue.maxConcurrentOperationCount = 2
    var firstResult: Int = 0
    let firstOperation = NSBlockOperation(block: {
    firstResult = 1
    })
    var secondResult: Int = 0
    let secondOperation = NSBlockOperation(block: {
    secondResult = 2
    })
    let finalOperation = NSBlockOperation(block: {
    let _ = firstResult + secondResult
    })

    finalOperation.addDependency(firstOperation)
    finalOperation.addDependency(secondOperation)

    myQueue.addOperation(firstOperation)
    myQueue.addOperation(secondOperation)
    myQueue.addOperation(finalOperation)



    Et tu sais comment que les calculs sont terminées ?



  • Et tu sais comment que les calculs sont terminées ?




    Tu le sais pas. Tu le sais pas plus que dans le code de colas_ d'ailleurs.


    J'ai simplement traduit ligne par ligne en Swift en évitant simplement un Warning.


    Lis les codes.

  • colas_colas_ Membre
    mars 2016 modifié #18

    Avec



    finalOperation.addDependency(firstOperation)
    finalOperation.addDependency(secondOperation)


    finalOperation ne sera pas lancée tant que ses dépendances ne sont pas terminées.


  • Donc finalOperation doit prévenir l'application que les calculs sont terminés avec une notification ?

  • MalaMala Membre, Modérateur

    Il me semble que NSOperationQueue propose waitUntilAllOperationsAreFinished non? Dans ce cas, nul besoin de finalOperation. On termine en synchrone dans le main thread.




  • Il me semble que NSOperationQueue propose waitUntilAllOperationsAreFinished non? Dans ce cas, nul besoin de finalOperation. On termine en synchrone dans le main thread.




    Ouais mais dans un cas concret on va bloquer le main thread et si les calculs sont longs on va bloquer l'UI.


    C'est surtout pratique si tu fais des operations qui ne durent pas trop longtemps comme des appels à  des outils console par exemple et dont tu veux récupérer la sortie standard.


  • var result: Int = 0

    let myQueue = NSOperationQueue()
    myQueue.maxConcurrentOperationCount = 2
    var firstResult: Int = 0
    let firstOperation = NSBlockOperation(block: {
    firstResult = 1
    })

    var secondResult: Int = 0
    let secondOperation = NSBlockOperation(block: {
    secondResult = 2
    })
    let finalOperation = NSBlockOperation(block: {
    result = firstResult + secondResult
    })
    finalOperation.completionBlock = {
    print ("Calculs terminés : ", result)
    }

    finalOperation.addDependency(firstOperation)
    finalOperation.addDependency(secondOperation)

    myQueue.addOperation(firstOperation)
    myQueue.addOperation(secondOperation)
    myQueue.addOperation(finalOperation)

  • MalaMala Membre, Modérateur
    mars 2016 modifié #23

    Mala, peux-tu nous mettre un schéma/exemple rapide avec les dispatch group ?


    Je vois pas quoi dire de plus que le lien que j'ai mis.


     


    Par exemple cela peut donner un truc comme ça en Obj-C...



    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
     
    // Limit number of threads
    dispatch_semaphore_t fd_sema = dispatch_semaphore_create(maxAllowedThreads);
     
    for( int num = 0; num<nbLoops; num++ )
    {
        dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
            
        // Launch block
        dispatch_group_async(group, queue, ^{
     
    // Do the job...

    // Send signal for next one
            dispatch_semaphore_signal(fd_sema);
        });
    }
     
    // Wait all jobs
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    dispatch_release(group);
    dispatch_release(fd_sema);

    A noter que dans cet exemple issu d'un code à  moi, je bride le nombre de threads avec un sémaphore. Je peux ainsi limiter l'usage mémoire à  façon sur des calculs gourmands.


  • J'avais lu quelque part que dispatch_sync est plutôt à  proscrire pour éviter les dead lock.


    Or, waitUntilAllOperationsAreFinished est plus ou moins un dispatch_sync.


  • MalaMala Membre, Modérateur
    mars 2016 modifié #25


    Ouais mais dans un cas concret on va bloquer le main thread et si les calculs sont longs on va bloquer l'UI.


    C'est surtout pratique si tu fais des operations qui ne durent pas trop longtemps comme des appels à  des outils console par exemple et dont tu veux récupérer la sortie standard.




    On est bien d'accord. En même temps là  si ses calculs sont long (et j'ai un doute tout de même) il risque de relancer plus vite qu'il n'a les retours. Donc conceptuellement cela demande d'en savoir plus.


  • MalaMala Membre, Modérateur


    J'avais lu quelque part que dispatch_sync est plutôt à  proscrire pour éviter les dead lock.


    Or, waitUntilAllOperationsAreFinished est plus ou moins un dispatch_sync.




    Je sais que j'ai déjà  rencontré un cas effectivement où cela posait souci mais ça remonte trop loin pour me souvenir du contexte. Je n'utilise plus que GCD en direct. Cela m'épargne une surcouche d'abstraction pour rien.

  • @mala : si tu ne veux pas bloquer le thread, comment ferais-tu ?


     


    Tu placerais tout le code



    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();

    // Limit number of threads
    dispatch_semaphore_t fd_sema = dispatch_semaphore_create(maxAllowedThreads);

    for( int num = 0; num<nbLoops; num++ )
    {
    dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);

    // Launch block
    dispatch_group_async(group, queue, ^{

    // Do the job...

    // Send signal for next one
    dispatch_semaphore_signal(fd_sema);
    });
    }

    // Wait all jobs
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    dispatch_release(group);
    dispatch_release(fd_sema);

    dans un dispatch_async(queue, ^{}); (avec la même queue) ?


  • MalaMala Membre, Modérateur

    Dans le lien que j'ai mis avant c'est évoqué. Au lieu de se mettre en attente, on passe juste par dispatch_group_notify() pour avoir une notification asynchrone.


  • colas_colas_ Membre
    mars 2016 modifié #29


    Je sais que j'ai déjà  rencontré un cas effectivement où cela posait souci mais ça remonte trop loin pour me souvenir du contexte. Je n'utilise plus que GCD en direct. Cela m'épargne une surcouche d'abstraction pour rien.




     


    A priori, quand je dois faire du calcul parallèle avec une conclusion, j'utilise la méthode donnée plus haut (NSOperation).


    Tu ne m'as pas convaincu avec les dispatch_group (je trouve le code moins lisible) d'autant plus qu'il faut s'abonner à  une notification si j'ai bien compris.


  • MalaMala Membre, Modérateur

    Je vois pas ce qui te gêne. dispatch_group_notify() fonctionne avec un simple bloc.  ???


     


    Fonctionnellement ta proposition est viable. C'est juste peu élégant (créer une NSOpération avec dépendance juste pour ça c'est capilotracté à  mon sens) en plus d'être moins performant.


  • Ok, je pensais que dispatch_group_notify() fonctionnait avec NSNotificationCenter.


Connectez-vous ou Inscrivez-vous pour répondre.