window.pipedriveLeadboosterConfig = { base : 'leadbooster-chat.pipedrive.com', companyId : 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version : 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster existe déjà') } else { w.LeadBooster = { q : [], on : function (n, h) { this.q.push({ t : 'o', n : n, h : h }) }, trigger : function (n) { this.q.push({ t : 't', n : n }) }, } } })() FORKING ET THREADING EN RUBY - The Codest
The Codest
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Industries
    • Fintech et banque
    • E-commerce
    • Adtech
    • Santé (Healthtech)
    • Fabrication
    • Logistique
    • Automobile
    • IOT
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
Flèche arrière RETOUR
2016-10-06
Développement de logiciels

FORKING ET THREADING EN RUBY

Marek Gierlach

Comme vous le savez probablement, Ruby dispose de plusieurs implémentations, telles que MRI, JRuby, Rubinius, Opal, RubyMotion, etc., et chacune d'entre elles peut utiliser un modèle d'exécution de code différent. Cet article se concentre sur les trois premières et compare MRI

Comme vous le savez probablement, Ruby a plusieurs implémentations, telles que MRI, JRuby, Rubinius, Opal, RubyMotion, etc. et chacune d'entre elles peut utiliser un modèle différent de code l'exécution. Cet article se concentre sur les trois premiers et compare MRI (l'implémentation la plus populaire) avec JRuby et Rubinius en exécutant quelques exemples de scripts censés évaluer l'adéquation du forking et du threading dans diverses situations, telles que le traitement d'algorithmes gourmands en ressources processeur, la copie de fichiers, etc.

Fourchette

  • est un nouveau processus enfant (une copie du processus parent)
  • a un nouvel identifiant de processus (PID)
  • a une mémoire séparée*
  • communique avec d'autres via des canaux de communication interprocessus (IPC) tels que des files d'attente de messages, des fichiers, des sockets, etc.
  • existe même lorsque le processus parent prend fin
  • est un appel POSIX - fonctionne principalement sur les plates-formes Unix

Fil

  • est "seulement" un contexte d'exécution, travaillant dans le cadre d'un processus
  • partage toute la mémoire avec les autres (par défaut, il utilise moins de mémoire qu'un fork)
  • communique avec les autres par le biais d'objets à mémoire partagée
  • meurt avec un processus
  • introduit des problèmes typiques de multithreading tels que la famine, les blocages, etc.

Il existe de nombreux outils utilisant des forks et des threads, qui sont utilisés quotidiennement, par exemple Unicorn (forks) et Puma (threads) au niveau des serveurs d'application, Resque (forks) et Sidekiq (threads) au niveau des tâches d'arrière-plan, etc.

Le tableau suivant présente la prise en charge du forking et du threading dans les principales implémentations de Ruby.

Mise en œuvre de RubyFourcheFiletage
IRMOuiOui (limité par le GIL**)
JRuby–Oui
RubiniusOuiOui

Deux autres mots magiques reviennent comme un boomerang dans ce sujet - parallélisme et concurrence - nous devons les expliquer un peu. Tout d'abord, ces termes ne peuvent pas être utilisés de manière interchangeable. En résumé, nous pouvons parler de parallélisme lorsque deux tâches ou plus sont traitées exactement en même temps. La concurrence a lieu lorsque deux tâches ou plus sont traitées dans des périodes de temps qui se chevauchent (pas nécessairement en même temps). Certes, il s'agit d'une explication générale, mais elle est suffisante pour vous aider à faire la différence et à comprendre le reste de cet article.

Rapport Fronented pour 2020

Le tableau suivant présente la prise en charge du parallélisme et de la concurrence.

Mise en œuvre de RubyParallélisme (via les fourches)Parallélisme (via les threads)Concurrence
IRMOuiNonOui
JRuby–OuiOui
RubiniusOuiOui (depuis la version 2.X)Oui

Finie la théorie, place à la pratique !

  • Le fait de disposer d'une mémoire séparée n'entraîne pas nécessairement une consommation identique à celle du processus parent. Il existe plusieurs techniques d'optimisation de la mémoire. L'une d'entre elles est le Copy on Write (CoW), qui permet au processus parent de partager la mémoire allouée avec le processus enfant sans la copier. Avec CoW, de la mémoire supplémentaire n'est nécessaire qu'en cas de modification de la mémoire partagée par un processus enfant. Dans le contexte de Ruby, toutes les implémentations ne sont pas compatibles avec la technique CoW. Par exemple, MRI la supporte entièrement depuis la version 2.X. Avant cette version, chaque fork consommait autant de mémoire qu'un processus parent.
  • L'un des principaux avantages/inconvénients de l'IRM (rayez la mention inutile) est l'utilisation du GIL (Global Interpreter Lock). En résumé, ce mécanisme est responsable de la synchronisation de l'exécution des threads, ce qui signifie qu'un seul thread peut être exécuté à la fois. Mais attendez... Cela signifie-t-il qu'il n'y a aucun intérêt à utiliser des threads dans l'IRM ? La réponse se trouve dans la compréhension des mécanismes internes de la GIL... ou au moins dans l'examen des exemples de code de cet article.

Cas de test

Afin de présenter le fonctionnement du forking et du threading dans les implémentations de Ruby, j'ai créé une classe simple appelée Test et quelques autres qui en héritent. Chaque classe a une tâche différente à traiter. Par défaut, chaque tâche s'exécute quatre fois en boucle. De plus, chaque tâche s'exécute contre trois types d'exécution de code : séquentielle, avec des fourches et avec des threads. En outre, Benchmark.bmbm exécute le bloc de code deux fois - la première fois pour faire fonctionner l'environnement d'exécution, la seconde pour effectuer des mesures. Tous les résultats présentés dans cet article ont été obtenus lors de la deuxième exécution. Bien entendu, même les bmbm ne garantit pas une isolation parfaite, mais les différences entre plusieurs exécutions du code sont insignifiantes.

exiger "benchmark"

classe Test
  AMOUNT = 4

  def run
    Benchmark.bmbm do |b|
      b.report("sequential") { sequential }
      b.report("forking") { forking }
      b.report("threading") { threading }
    end
  end

  private

  def sequential
    AMOUNT.times { perform }
  end

  def forking
    AMOUNT.times do
      fork do
        exécuter
      fin
    fin

    Process.waitall
  rescue NotImplementedError => e
    # la méthode fork n'est pas disponible dans JRuby
    met e
  fin

  def threading
    threads = []

    AMOUNT.times do
      threads << Thread.new do
        exécuter
      end
    fin

    threads.map(&:join)
  fin

  def perform
    raise "non implémenté"
  end
end
Test de charge

Exécute des calculs en boucle pour générer une charge importante de l'unité centrale.

classe LoadTest < Test
  def perform
    1000.times { 1000.times { 2**3**4 } }
  end
end

Exécutons-le...

LoadTest.new.run

...et vérifier les résultats

IRMJRubyRubinius
séquentiel1.8629282.0890001.918873
bifurcation0.945018–1.178322
filetage1.9139821.1070001.213315

Comme vous pouvez le constater, les résultats des exécutions séquentielles sont similaires. Bien sûr, il y a une petite différence entre les solutions, mais elle est due à l'implémentation sous-jacente des méthodes choisies dans les différents interprètes.

La bifurcation, dans cet exemple, permet un gain de performance significatif (le code s'exécute presque deux fois plus vite).

Le threading donne les mêmes résultats que le forking, mais seulement pour JRuby et Rubinius. L'exécution de l'échantillon avec des threads sur l'IRM prend un peu plus de temps que la méthode séquentielle. Il y a au moins deux raisons à cela. Premièrement, GIL force l'exécution séquentielle des threads, donc dans un monde parfait le temps d'exécution devrait être le même que pour l'exécution séquentielle, mais il y a aussi une perte de temps pour les opérations GIL (passage d'un thread à l'autre, etc.). Deuxièmement, la création de threads nécessite également un certain temps de latence.

Cet exemple ne permet pas de répondre à la question du sens des fils d'utilisation dans l'IRM. Voyons-en un autre.

Test du sommeil

Exécute une méthode de sommeil.

classe SnoozeTest < Test
  def perform
    sleep 1
  end
end

Voici les résultats

IRMJRubyRubinius
séquentiel4.0046204.0060004.003186
bifurcation1.022066–1.028381
filetage1.0015481.0040001.003642

Comme vous pouvez le voir, chaque implémentation donne des résultats similaires non seulement dans les exécutions séquentielles et de forking, mais aussi dans les exécutions de threading. Alors, pourquoi MRI a le même gain de performance que JRuby et Rubinius ? La réponse se trouve dans l'implémentation de dormir.

IRM dormir est mise en œuvre avec la méthode rb_thread_wait_for C, qui en utilise une autre appelée native_sleep. Jetons un coup d'oeil rapide à son implémentation (le code a été simplifié, l'implémentation originale peut être trouvée ici):

static void
native_sleep(rb_thread_t *th, struct timeval *timeout_tv)
{
  ...

  GVL_UNLOCK_BEGIN() ;
  {
    // faire des choses ici
  }
  GVL_UNLOCK_END() ;

  thread_debug("native_sleep donen") ;
 }

La raison pour laquelle cette fonction est importante est qu'en plus d'utiliser le contexte strict de Ruby, elle bascule également dans celui du système afin d'y effectuer certaines opérations. Dans une telle situation, le processus Ruby n'a rien à faire... Bel exemple de perte de temps ? Pas vraiment, car il y a une GIL qui dit : "Rien à faire dans ce thread ? Passons à une autre et revenons ici après un certain temps". Cela peut être fait en déverrouillant et en verrouillant la GIL avec GVL_UNLOCK_BEGIN() et GVL_UNLOCK_END() fonctions.

La situation devient claire, mais dormir est rarement utile. Nous avons besoin de plus d'exemples concrets.

Test de téléchargement de fichiers

Exécute un processus qui télécharge et enregistre un fichier.

nécessite "net/http"

classe DownloadFileTest < Test
  def perform
    Net::HTTP.get("upload.wikimedia.org", "/wikipedia/commons/thumb/7/73/Ruby_logo.svg/2000px-Ruby_logo.svg.png")
  fin
fin

Les résultats suivants n'ont pas besoin d'être commentés. Ils sont assez similaires à ceux de l'exemple précédent.

1.003642JRubyRubinius
séquentiel0.3279800.3340000.329353
bifurcation0.104766–0.121054
filetage0.0857890.0940000.088490

Un autre bon exemple pourrait être le processus de copie de fichiers ou toute autre opération d'entrée/sortie.

Conclusions

  • Rubinius supporte pleinement le forking et le threading (depuis la version 2.X, lorsque GIL a été supprimé). Votre code peut être concurrent et s'exécuter en parallèle.
  • JRuby fait un bon travail avec les threads, mais ne supporte pas du tout le forking. Le parallélisme et la concurrence peuvent être réalisés avec des threads.
  • IRM supporte le forking, mais le threading est limité par la présence de GIL. La simultanéité peut être obtenue avec des threads, mais uniquement lorsque le code en cours d'exécution sort du contexte de l'interpréteur Ruby (par exemple les opérations d'E/S, les fonctions du noyau). Il n'y a aucun moyen d'obtenir du parallélisme.

Articles connexes

Développement de logiciels

Construire des applications web à l'épreuve du temps : les conseils de l'équipe d'experts de The Codest

Découvrez comment The Codest excelle dans la création d'applications web évolutives et interactives à l'aide de technologies de pointe, offrant une expérience utilisateur transparente sur toutes les plateformes. Découvrez comment notre expertise favorise la transformation numérique et la...

LE CODEST
Développement de logiciels

Les 10 premières entreprises de développement de logiciels basées en Lettonie

Découvrez les principales sociétés de développement de logiciels en Lettonie et leurs solutions innovantes dans notre dernier article. Découvrez comment ces leaders de la technologie peuvent vous aider à développer votre entreprise.

thecodest
Solutions pour les entreprises et les grandes entreprises

L'essentiel du développement de logiciels Java : Un guide pour une externalisation réussie

Explorez ce guide essentiel sur le développement réussi de logiciels Java outsourcing pour améliorer l'efficacité, accéder à l'expertise et assurer la réussite des projets avec The Codest.

thecodest
Développement de logiciels

Le guide ultime de l'externalisation en Pologne

L'essor de outsourcing en Pologne est dû aux progrès économiques, éducatifs et technologiques, qui favorisent la croissance des technologies de l'information et un climat propice aux entreprises.

TheCodest
Solutions pour les entreprises et les grandes entreprises

Le guide complet des outils et techniques d'audit informatique

Les audits informatiques garantissent la sécurité, l'efficacité et la conformité des systèmes. Pour en savoir plus sur leur importance, lisez l'article complet.

The Codest
Jakub Jakubowicz CTO & Co-Fondateur

Abonnez-vous à notre base de connaissances et restez au courant de l'expertise du secteur des technologies de l'information.

    A propos de nous

    The Codest - Entreprise internationale de développement de logiciels avec des centres technologiques en Pologne.

    Royaume-Uni - Siège

    • Bureau 303B, 182-184 High Street North E6 2JA
      Londres, Angleterre

    Pologne - Les pôles technologiques locaux

    • Parc de bureaux Fabryczna, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Varsovie, Pologne

      The Codest

    • Accueil
    • A propos de nous
    • Services
    • Études de cas
    • Savoir comment
    • Carrières
    • Dictionnaire

      Services

    • Conseil consultatif
    • Développement de logiciels
    • Développement backend
    • Développement frontal
    • Staff Augmentation
    • Développeurs backend
    • Ingénieurs en informatique dématérialisée
    • Ingénieurs des données
    • Autres
    • Ingénieurs AQ

      Ressources

    • Faits et mythes concernant la coopération avec un partenaire externe de développement de logiciels
    • Des États-Unis à l'Europe : Pourquoi les startups américaines décident-elles de se délocaliser en Europe ?
    • Comparaison des pôles de développement Tech Offshore : Tech Offshore Europe (Pologne), ASEAN (Philippines), Eurasie (Turquie)
    • Quels sont les principaux défis des CTO et des DSI ?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Conditions d'utilisation du site web

    Copyright © 2025 par The Codest. Tous droits réservés.

    fr_FRFrench
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek fr_FRFrench