Nicolas Cavigneaux
30 11 2010

MacRuby - Gérer des tâches asynchrones avec GCD

posté par dans les catégories technologies, alternatives, interpréteur, apple

GCD, qu’est ce que c’est ?

Comme expliqué dans le précédent article sur MacRuby, Ruby a été pensé à l’origine pour fonctionner sur des machines mono-processeur. L’interpréteur officiel Ruby (MRI) utilise des threads émulés (green threads) pour simuler le multi-tâches. Malheureusement cette façon de procéder ne permet pas de tirer partie des processeurs multi-coeurs. Avec les green threads, les tâches ne sont pas réellement exécutées en parallèle puisqu’elles le sont toutes sur le même processeur.

MacRuby a pris le parti de faire sauter le verrou global qui a jusque lors régissait le fonctionnement de l’interpréteur Ruby et d’utiliser des threads natifs. Récemment, avec l’arrivée de Mac OS X 10.6, MacRuby a pu pousser cette tendance encore plus loin en ajoutant le support de GCD. Grand Central Dispatch est une technologie mise au point par Apple pour faciliter la vie des développeurs. GCD va leur permettre d’utiliser en toute facilité la puissance des processeurs multi-coeurs. Utiliser GCD permet de masquer au développeur toutes les subtilités et les détails de la gestion des threads, plus besoin pour lui de créer, gérer et détruire ses threads POSIX. Le gros avantage de cette technologie est qu’il devient trivial de mettre en place un morceau de code qui utilisera toute la puissance de la machine pour des traitements lourds.

Disponible en premier lieu uniquement en Objective-C, l’équipe de MacRuby a récemment sortie une nouvelle version de l’interpréteur capable de tirer partie de cette technologie. On peut donc maintenant, depuis notre langage préféré, utiliser de vrais threads et ce en plus de manière très simple.

Les files d’attente

Les files d’attente (Queues) sont des structures de données qui permettent d’exécuter des tâches de manière concurrente ou séquentielle. Une file d’attente séquentielle exécutera les tâches dans l’ordre reçu, l’une après l’autre, alors qu’une file d’attente concurrentielle peut exécuter plusieurs tâches à la fois.

Dans MacRuby, les files d’attente sont gérer par la classe Dispatch::Queue. GCD maintient, pour le développeur, un ensemble de threads POSIX et s’occupe de dispatcher les tâches. GCD va également se charger de distribuer les tâches aux processeurs disponible sans que le développeur n’ai à s’en soucier.

GCD offre 3 types de files d’attente :

  • main : les tâches s’exécutent en série sur le thread principal de l’application
  • concurrent : les tâches s’exécutent dans l’ordre FIFO mais peuvent être exécutées de manière concurrentes
  • serial : les tâches s’exécutent une par une dans l’ordre FIFO

Création d’une file d’attente

Créer et gérer une file d’attente est très simple via GCD :

# Création d’une file d’attente en série queue = Dispatch::Queue.new(‘org.synbioz.gcd’)

# Dispatcher un traitement de manière synchrone queue.sync do puts ‘Travail synchrone.’ sleep 5.0 puts ‘Travail synchrone terminé !’ end

# Dispatcher un traitement de manière asynchrone queue.async do puts ‘Travaille asynchrone.’ sleep 5.0 puts ‘Travail asynchrone terminé !’ end

Le code exécuté dans le bloc #sync sera bloquant pour la file d’attente alors que le bloc #async sera exécuté en arrière plan sans bloquer la file.

Les files d’attente ont de nombreux avantages par rapport aux threads POSIX ou aux threads Ruby. Les threads sont très gourmands en terme de mémoire et s’avèrent lourds à créer et à détruire. Les filles d’attente sont elles au contraire légères, il est tout à fait possible de créer des milliers de files d’attente en même temps ce qui serait impossible ou particulièrement lent avec des threads POSIX. Un autre avantage notoire de GCD est que son implémentation tire parti du système de répartition des charges géré par le système.

Synchronisation

S’assurer que les méthodes et les données ne sont utilisées que par un et un seul thread à la fois est un problème récurrent. Ruby, de base, ne prévoit aucun outil pour gérer la synchronisation, il nous fait passer par les classes Mutex et Monitor. GCD amène avec lui une façon bien plus élégante de procéder, de plus haut niveau que les Mutex et bien plus simple à utiliser que Monitor.

class Calculation def initialize @queue = Dispatch::Queue.new(‘org.synbioz.gcd.synchro’) end

def calculate
  @queue.sync do
    # Ici une section critique de code qui ne doit être accédé que par un thread à la fois
    calculation
  end
end   end

Il est à noter que les files d’attente série fonctionne sur le principe du FIFO ce qui nous assure qu’un seul thread exécutera notre bloc de code à un instant T.

Les groupes

Lorsqu’on travaille avec des files d’attente, on a parfois besoin de savoir que toutes les tâches de cette dernière ont été exécutées. GCD fournit la classe Group pour gérer ce cas. La classe Group permet de synchroniser une file d'attente en toute simplicité. En passant un group en paramètre à #async ou à #sync, vous enregistrez la tâche dans la file comme appartenant à un groupe de tâches. Une fois des tâches enregistrées dans un groupe, vous pouvez soit attendre que toutes les tâches du groupe soient finies grâce à la méthode wait que vous appellerez sur votre groupe ou pouvez également enregistrer un bloc de code, via la méthode notify`, qui devra être exécuté lorsque le groupe aura fini d’exécuter ses tâches.

Exemple d’utilisation des groupes

L’une des principales utilités des exécutions parallèles est de faire des traitements lourds en calculs en arrière plan. On peut alors utiliser les files d’attente GCD pour exécuter les tâches en coordination avec les groupes pour synchroniser l’exécution des tâches. Voici comment on pourrait implémenter ce système :

include Dispatch class Computation def initialize(&block) # Chaque thread a sa propre file d’attente FIFO dans laquelle sera dispatché # l’exécution du code passé à la variable &block. Thread.current[:computations] ||= Queue.new(“org.synbioz.gcd.computations-#{Thread.current.object_id}”) @group = Group.new # On dispatche de façon asynchrone le traitement à la file d’attente du thread. Thread.current[:computations].async(@group) { @value = block.call } end

def value
  # On attent que les calculs soient finis
  @group.wait
# On retourne la valeur calculée
  @value
end   end

Et un appel à ce système qui fera nos calculs en arrière plan :

result = Computation.new do puts ‘Long travail de calcul en arrière plan.’ sleep 10 rand end

result.value

Traitements en parallèle

Il est également très simple de mettre en place du code avec exécution en parallèle grâce aux groupes et aux files d’attente concurrentes fournies par GCD :

# Nous utiliseront ce tableau dans la file puisque l’interpréteur # n’a pas de Global Interpreter Lock et ne peut donc pas nous # assurer un accès thread-safe au tableau result = []

# Utilisation d'un groupe pour synchroniser l'exécution du bloc
group = Dispatch::Group.new

result_queue = Dispatch::Queue.new('org.synbioz.concurrent-queue.#{result.object_id}')
0.upto(1000) do
  # Envoie la tâche à la file d'attente concurrente par défaut
  Dispatch::Queue.concurrent.async(group) do
    res = complicated_computation
    result_queue.async(group) { result[idx] = res }
  end
end

# On attend que tous les blocs aient été exécutés
group.wait   # On retourne le résultat final
result

Conclusion

Si vous avez besoin d’optimiser des morceaux de votre code pour que ceux-ci s’exécute plus vite, utiliser la parallèlisation sera souvent la solution. Il y a bien d’autres choses encore que vous pouvez faire avec GCD, par exemple Dispatch::Semaphore est un outil puissant permettant aux threads de communiquer entre eux, de gérer et partager les ressources. Nous avons également Dispatch::Source qui fournit un moyen élégant de faire de la programmation basée sur des événements.

Si le sujet vous intéresse, je vous conseille vivement de continuer à vous documenter et d’expérimenter via MacRuby, vous verrez qu’il devient rapidement très simple de gérer des processus en parallèle et donc d’améliorer la réactivité de son application.

L’équipe Synbioz.

Libres d’être ensemble.

Commentaires (2) Flux RSS des commentaires

  • Renaud Morvan

    01/12/2010 à 03:11

    D'abord merci pour ces articles sur MacRuby en Francais, c'est très intéressant et suffisamment rare pour être souligné.

    Petite remarque sur cette article et le précédant, les threads ne sont "green" que sur la MRI en 1.8.X, la version 1.9.X utilise des threads natifs. Ceci dit le verrou global (GIL) reste présent sur la 1.9.X et empèche l'execution en parallèle (et donc multi coeur) du code Ruby pure (mais n'empèche pas le parallélisme au niveau de certaines librairies C et donc d'appels réseaux bloquant).

    Les Fibres/Continuations présentent en général une alternative intéressante aux threads en terme de concurrence sur la MRI 1.9. Pour des cas d'usage plus proche de GCD, zeroMQ pourrait être une alternative cross implémentation viable. Ceci dit pour l'instant rien d'aussi simple et direct que ce qui présenté dans cet article sur macRuby mais qui ne sera malheureusement jamais porté sur les autres implémentations.

    Petites questions, quels cas d'usages pour macRuby ? Application desktop Mac ? Quid du futur Apple Store pour mac ?

  • Nicolas Cavigneaux

    01/12/2010 à 11:33

    Bonne remarque, concernant les threads, merci pour cette précision.

    Ce qu'on peut espérer c'est que MacRuby soit porté sur d'autres plate-formes. Visiblement il n'y a aucun problème technique qui pourrait empêcher ça. GCD étant basé sur la librairie open-source libdispatch, il serait donc envisageable d'avoir les bénéfices de GCD sur d'autres plate-formes. Est-ce qu'on peut imaginer que cette lib soit utilisée dans d'autres implémentations de l'interpréteur, je ne sais pas mais ça me paraît possible.

    Pour ma part utiliser MacRuby c'est principalement pour développer du desktop pour Mac mais je me rend compte à l'utilisation que de plus en plus j'aime travailler avec cette version de l'interpréteur pour des petits scripts et utilitaires qui tournent sur ma machine (un Mac).

    C'est selon moi la meilleure implémentation disponible mais mon avis est peut-être biaisé par le fait que je sois fan de Mac et que donc cette implémentation m'apporte tout ce dont je peux rêver pour développer sur ma machine.

    Concernant l'Apple Store pour Mac, je ne suis pas trop les nouvelles, je ne suis pas sûr que le modèle appliqué à l'iPhone puisse fonctionner pour nos machines de bureau ...

Ajouter un commentaire

Notre expérience vous intéresse ? Inscrivez-vous à nos articles !

×

Newsletter

Join us

Continue the conversation

Phone
0 805 69 35 35

Our latest news

Our latest tweets

Présentation d’#angular avec exemples à l’appui https://t.co/kyM0HPtqNQ

#hackademy Et vous, quels sujets aimeriez vous voir traités en vidéo ?

Pour échanger autour de nos vidéos, rendez vous directement sur notre chaine Youtube https://t.co/YHSF65VEqI