Machines d'état vs threads

24

Alan Cox a dit un jour : "Un ordinateur est une machine d'état. Les threads sont destinés aux personnes qui ne peuvent pas programmer de machines d'état".
Puisque demander à Alan directement n'est pas une option pour moi, je préfère demander ici: comment peut-on obtenir une fonctionnalité multi-threading dans un langage de haut niveau, tel que Java, en utilisant un seul thread et une machine d'état? Par exemple, que faire s'il y a 2 activités à effectuer (faire des calculs et faire des E / S) et qu'une activité peut bloquer?
L'utilisation d'une machine à états uniquement est-elle une alternative viable au multithread dans les langues de haut niveau?

Victor Sorokin
la source
4
Un ordinateur est aussi une machine de Turing. Néanmoins, il n'est pas nécessairement utile de le programmer comme une machine de Turing. La pile dans les langages impératifs est une chose extrêmement utile, et les programmes multithread permettent de garder plusieurs piles en mémoire en même temps. Faire de même dans une machine d'état est certainement possible, mais d'autant plus compliqué.
thiton
10
Alan était un développeur de noyau OS; c'était son domian . Sa citation doit donc être prise dans ce contexte. Il aurait programmé «contre le métal» où il est plus logique d'utiliser un tel modèle. Une fois que le système d'exploitation a supprimé le matériel et ses propriétés intrinsèques (qu'un "ordinateur est une machine d'état ..."), vous avez la possibilité et l'avantage de pouvoir utiliser d'autres modèles qui ont plus de sens dans votre domaine . Presque tous les jeux font un usage intensif des machines d'état.
Steven Evers
2
Le threading est simplement une fonctionnalité du système d'exploitation pour gérer automatiquement certains de vos commutateurs de machine d'état, si vous le souhaitez. De toute évidence, vous pouvez créer une énorme machine à états qui gérera tout par ses propres moyens, mais c'est plus compliqué. On peut en dire autant des processus. Vous pouvez dire que les processus sont destinés aux personnes qui ne peuvent pas non plus programmer des machines à états. Mais l'abstraction vous offre une interface beaucoup plus simple et moins sujette aux erreurs. À mon avis, c'est juste une autre "citation cool" qui devrait être entendue, envisagée, puis ignorée en réalité.
Yam Marcovic
2
"Mais l'abstraction [thread] vous offre une interface beaucoup plus simple et moins sujette aux erreurs." Cela semble faux. Le nombre de personnes qui se trompent sur la sécurité des threads indique qu'il semble provoquer des erreurs.
S.Lott
1
Beaucoup de commentaires et de réponses ici interprètent la citation comme étant anti-multitâche en général; Je crois qu'Alan Cox est simplement anti-threads et préconiserait l'utilisation de plusieurs processus pour atteindre de nombreux objectifs pour lesquels les gens utilisent des threads. Gardez à l'esprit qu'il est un hacker Unix: fork FTW. Je n'ai trouvé aucun commentaire de lui directement sur la citation, mais en voici un de Larry McVoy de la liste de diffusion du noyau Linux qui va dans ce sens: lkml.indiana.edu/hypermail/linux/kernel/0106.2/0405.html
Martin B

Réponses:

25

Un thread ne fait qu'entrelacer des opérations de sorte que certaines parties du processus semblent se chevaucher dans le temps. Une machine monocœur à plusieurs threads se contente de sauter: elle exécute de petits morceaux de code à partir d'un thread, puis passe à un autre thread. Un ordonnanceur simple décide quel thread a la priorité la plus élevée et est réellement exécuté dans le noyau.

Sur un ordinateur monocœur, rien ne se passe réellement "en même temps". C'est juste une exécution entrelacée.

Il existe de très nombreuses façons de réaliser l'entrelacement. Beaucoup.

Supposons que vous ayez un processus simple à deux threads qui utilise un verrou simple afin que les deux threads puissent écrire dans une variable commune. Vous avez six blocs de code.

  • Serrure T1 avant
  • T1-avec serrure
  • Serrure T1 après
  • Serrure T2-avant
  • T2-avec serrure
  • Serrure T2 après

[Cela peut être dans une boucle ou avoir plus de verrous ou autre. Tout cela ne fait que s'allonger, pas être plus complexe.]

Les étapes de T1 doivent s'exécuter dans l'ordre (T1-avant, T1-avec, T1-après) et les étapes de T2 doivent s'exécuter dans l'ordre (T2-avant, T2-avec, T2-après).

Outre la contrainte "dans l'ordre", celles-ci peuvent être entrelacées de n'importe quelle manière. En tous cas. Ils peuvent être exécutés comme indiqué ci-dessus. Un autre ordre valide est (T1-avant, T2-avant, T2-verrou, T1-verrou, T2-après, T1-après). Il y a beaucoup de commandes valides.

Attendez.

Ce n'est qu'une machine à états avec six états.

Il s'agit d'un automate à états finis non déterministe. L'ordre des états T1-xxx avec les états T2-xxx est indéterminé et n'a pas d'importance. Il y a donc des endroits où le "prochain état" est un tirage au sort.

Par exemple, lorsque le FSM démarre, T1-avant ou T2-avant sont tous deux des premiers états légitimes. Un tirage au sort.

Disons que c'est venu T1-avant. Faites ça. Lorsque cela est fait, vous avez le choix entre T1 avec et T2 avant. Un tirage au sort.

À chaque étape du FSM, il y aura deux choix (deux fils - deux choix) et un tirage au sort peut déterminer quel état spécifique est suivi.

S.Lott
la source
Merci, belle explication. Et qu'en est-il de la machine multicœur? Je suppose qu'il n'y a aucun moyen explicite d'exploiter les cœurs dans la machine d'état Java? Il faut compter sur OS pour cela, non?
Victor Sorokin
5
Une machine multicœur rend la planification un peu plus complexe. Cependant, tous les cœurs écrivent dans une seule mémoire commune, de sorte que l'ordre d'écriture en mémoire entre les deux cœurs signifie que - essentiellement - nous sommes de retour à l'exécution entrelacée des écritures en mémoire. Le système d'exploitation exploite les cœurs et la JVM en tire parti. Pas besoin d'y réfléchir à deux fois.
S.Lott
9

L'écriture de fonctions de blocage est destinée aux personnes qui ne peuvent pas créer de machines d'état;)

Les discussions sont utiles si vous ne pouvez pas contourner le blocage. Aucune activité informatique fondamentale ne bloque vraiment, c'est juste que beaucoup d'entre eux sont mis en œuvre de cette façon pour une facilité d'utilisation. Au lieu de renvoyer un caractère ou "échec de lecture", une fonction de lecture se bloque jusqu'à ce que tout le tampon soit lu. Au lieu de vérifier le message de retour dans une file d'attente et de retourner s'il n'en trouve aucun, une fonction de connexion attend la réponse.

Vous ne pouvez pas utiliser les fonctions de blocage dans une machine d'état (au moins une qui ne peut pas être autorisée à "geler").

Et oui, l'utilisation de la machine d'état est une alternative viable. Dans les systèmes en temps réel, c'est la seule option, le système fournissant un cadre pour la machine. L'utilisation de threads et de fonctions de blocage n'est que "la solution la plus simple", car généralement un appel à une fonction de blocage remplace environ 3-4 états dans la machine d'état.

SF.
la source
Une erreur de page de code dans un programme à contexte unique d'exécution est fondamentalement vraiment bloquante. Par définition, le code qui a un seul contexte d'exécution ne peut pas avancer jusqu'à ce que le prochain morceau de code du flux soit disponible.
David Schwartz
1
@David Schwartz: c'est vrai, c'est fondamentalement bloquant; mais ce n'est pas une «opération» puisque ce n'est pas quelque chose que fait le code bloqué, c'est quelque chose qui lui arrive.
Javier
1
La lecture de fichiers n'est pas fondamentalement bloquante - elle peut toujours être divisée en demandant la lecture de l'emplacement donné et en acquérant les données des tampons après que la demande a été satisfaite. Et un défaut de page est une solution de contournement pour l'utilisation de swap au hasard / heuristique. Cela se produira si un état donné a été entré avant que toutes les données nécessaires à son exécution ne soient disponibles - un manque de prévoyance, quelque chose contre le concept de machine d'état. Si les opérations de swap-out et de swap-in font partie de la machine d'état, un défaut de page ne se produira pas.
SF.
1
@David Schwartz: La définition du comportement de "blocage" est toujours soumise à des exigences "en temps réel". Par exemple: une erreur de page de code est considérée comme non bloquante pour une application qui nécessite une réactivité de l'ordre de centaines de millisecondes. D'un autre côté, si l'application a des exigences strictes en temps réel, elle n'utiliserait pas du tout de mémoire virtuelle.
MaR
1
@Mar: ... ou utilisez un algorithme de permutation déterministe qui garantit l'extraction des données requises avant qu'elles ne deviennent nécessaires.
SF.
9

Comment peut-on obtenir une fonctionnalité multi-threading dans un langage de haut niveau, tel que Java, en utilisant un seul thread et une machine d'état? Par exemple, que faire s'il y a 2 activités à effectuer (faire des calculs et faire des E / S) et qu'une activité peut bloquer?

Ce que vous décrivez s'appelle le multitâche coopératif , où les tâches reçoivent le processeur et devraient y renoncer volontairement après un certain temps ou une activité auto-déterminée. Une tâche qui ne coopère pas en continuant à utiliser le CPU ou en bloquant gomme tout le travail et à moins d'avoir une horloge de surveillance matérielle, il n'y a rien que le code supervisant les tâches puisse faire à ce sujet.

Ce que vous voyez dans les systèmes modernes est appelé multitâche préemptif , c'est-à-dire que les tâches n'ont pas à abandonner le processeur parce que le superviseur le fait pour elles lorsqu'une interruption générée par le matériel arrive. La routine de service d'interruption dans le superviseur enregistre l'état du CPU et le restaure la prochaine fois que la tâche est considérée comme méritant une tranche de temps, puis restaure l'état de la tâche à exécuter ensuite et y revient comme si rien ne s'était passé . Cette action est appelée un changement de contexte et peut être coûteuse.

L'utilisation d'une machine à états uniquement est-elle une alternative viable au multithread dans les langues de haut niveau?

Viable? Sûr. Sain? Parfois. Que vous utilisiez des threads ou une forme de multitâche coopératif fait maison (par exemple, des machines d'état) dépend des compromis que vous êtes prêt à faire.

Les threads simplifient la conception des tâches au point où vous pouvez traiter chacun comme son propre programme qui partage l'espace de données avec d'autres. Cela vous donne la liberté de vous concentrer sur le travail à accomplir et non sur toute la gestion et l'entretien ménager requis pour le faire fonctionner une itération à la fois. Mais comme aucune bonne action n'est impunie, vous payez pour toute cette commodité dans des changements de contexte. Avoir de nombreux threads qui produisent le CPU après avoir effectué un travail minimal (volontairement ou en faisant quelque chose qui bloquerait, comme les E / S) peut consommer beaucoup de temps processeur pour changer de contexte. Cela est particulièrement vrai si vos opérations de blocage se bloquent rarement très longtemps.

Il y a des situations où la route coopérative a plus de sens. Une fois, j'ai dû écrire un logiciel utilisateur pour un morceau de matériel qui diffusait de nombreux canaux de données via une interface mappée en mémoire qui nécessitait une interrogation. Chaque canal était un objet construit de telle manière que je pouvais le laisser s'exécuter en tant que thread ou exécuter plusieurs fois un cycle d'interrogation.

Les performances de la version multithread n'étaient pas bonnes du tout pour la raison que j'ai décrite ci-dessus: chaque thread effectuait un travail minimal et produisait ensuite le CPU afin que les autres canaux puissent avoir un peu de temps, provoquant de nombreux changements de contexte. Le fait de laisser les threads s'exécuter gratuitement jusqu'à ce qu'ils soient préemptés a contribué au débit, mais certains canaux n'ont pas été réparés avant que le matériel ne subisse un dépassement de tampon car ils n'ont pas obtenu une tranche de temps assez tôt.

La version à un seul fil, qui effectuait même des itérations de chaque canal, fonctionnait comme un singe échaudé et la charge sur le système tombait comme un rocher. La pénalité que j'ai payée pour la performance supplémentaire était d'avoir à jongler avec les tâches moi-même. Dans ce cas, le code pour le faire était suffisamment simple pour que le coût de développement et de maintenance en vaille la peine. Je suppose que c'est vraiment le résultat final. Si mes fils avaient été ceux qui étaient restés assis à attendre le retour d'un appel système, l'exercice n'aurait probablement pas valu la peine.

Cela m'amène au commentaire de Cox: les threads ne sont pas exclusivement réservés aux personnes qui ne peuvent pas écrire de machines d'état. Certaines personnes sont tout à fait capables de le faire, mais choisissent d'utiliser une machine d'état en conserve (c'est-à-dire un thread) dans le but de faire le travail plus tôt ou avec moins de complexité.

Blrfl
la source
2

que faire s'il y a 2 activités à effectuer (faire des calculs et faire des E / S) et qu'une activité peut bloquer?

Eh bien, honnêtement, je ne peux pas imaginer comment gérer le blocage des E / S sans threads. C'est ce qu'on appelle le blocage après tout simplement parce que le code qui l'invoque doit le faire wait.

D'après ma lecture de l'e-mail original de Cox (ci-dessous), il souligne que le filetage ne s'adapte pas bien. Je veux dire, que faire s'il y a 100 demandes d'E / S? 1000? 10000? Cox souligne que le fait d'avoir un grand nombre de threads peut entraîner de graves problèmes:

De: Alan Cox ([email protected])
Date: ven 21 janvier 2000-13: 33: 52 EST

le document IBM), que si votre application dépend d'un grand nombre de threads, vous allez toujours vous heurter au planificateur? beaucoup de gens jettent beaucoup de fils sur un problème et cela peut vraiment être une mauvaise conception.

C'est le moindre de vos soucis. 1000 threads, c'est 8 Mo de piles de noyau, et suffisamment de commutation de tâches pour être sûr que vous pourriez aussi bien désactiver la plupart de votre cache. Un ordinateur est une machine d'état. Les threads sont destinés aux personnes qui ne peuvent pas programmer de machines d'état.

Il y a de nombreux cas où Linux n'aide certainement pas la situation, notamment les E / S de blocs asynchrones.

Alan

source: Re: Analyse intéressante du threading du noyau linux par IBM (archives de la liste de diffusion linux-kernel)

moucheron
la source
1
  • En théorie, c'est vrai. Dans la vraie vie, les threads ne sont qu'une abstraction efficace utilisée pour programmer une telle machine à états. Ils sont si efficaces qu'ils peuvent également être utilisés pour programmer des diagrammes statiques et des réseaux de Petri (c'est-à-dire des comportements parallèles, où les machines d'état sont essentiellement séquentielles).

  • Le problème avec les machines à états est l'explosion combinatoire. Le nombre d'états d'un ordinateur avec 4G RAM est de 2 ^ (2 ^ 32) états (sans compter le lecteur de disque 2T).

  • Pour un homme dont le seul outil est un marteau, chaque problème ressemble à un clou.

mouviciel
la source
1

Les threads sont la seule option dans deux cas:

  • pour utiliser plusieurs cœurs sans séparation de mémoire.
  • pour faire face au code externe qui bloque.

La seconde est pourquoi la plupart des gens pensent que les threads sont inévitables pour faire des IO ou de la programmation réseau, mais c'est généralement parce qu'ils ne savent pas que leur système d'exploitation a une API plus avancée (ou ne veulent pas se battre avec son utilisation).

En ce qui concerne la facilité d'utilisation et la lisibilité, il existe toujours des boucles d'événements (comme libev ou EventMachine ) qui rendent la programmation d'une machine d'état presque aussi simple que de le faire avec des threads, tout en donnant suffisamment de contrôle pour oublier les problèmes de synchronisation.

mbq
la source
1
Le meilleur des deux mondes: les petites machines à états bloquantes dans les threads rendent le code d'application très simple. Et les fils se divisent bien en noyaux si vous les avez. Si tout n'a pas au moins deux cœurs, ce sera bientôt. c'est-à-dire les téléphones à base de bras quad core à venir 2012.
Tim Williscroft
0

Un bon moyen de comprendre la façon dont les machines d'état et le multithreading interagissent est de regarder les gestionnaires d'événements GUI. De nombreuses applications / frameworks GUI utilisent un seul thread GUI qui interrogera les sources d'entrée possibles et appellera une fonction pour chaque entrée reçue; essentiellement, cela pourrait être écrit comme un énorme commutateur:

while (true) {
    switch (event) {
        case ButtonPressed:
        ...
        case MachineIsBurning:
        ....
    }
}

Maintenant, il devient clair assez rapidement que le niveau de contrôle de haut niveau dans cette construction ne peut pas être élevé: le gestionnaire de ButtonPressed doit terminer sans interaction avec l'utilisateur et retourner à la boucle principale, car si ce n'est pas le cas, aucun autre événement utilisateur peut être traité. S'il a un état à enregistrer, cet état doit être enregistré dans des variables globales ou statiques, mais pas sur la pile; c'est-à-dire que le flux de contrôle normal dans un langage impératif est limité. Vous êtes essentiellement limité à une machine d'état.

Cela peut devenir assez compliqué lorsque vous avez des sous-programmes imbriqués qui doivent enregistrer, par exemple, un niveau de récursivité. Ou vous êtes en train de lire un fichier, mais le fichier n'est pas disponible pour le moment. Ou sont juste dans un long calcul. Dans tous ces cas, il devient souhaitable de sauvegarder l'état de l'exécution en cours et de revenir à la boucle principale, et c'est du multithreading . Ni plus ni moins.

Le tout est devenu un peu plus compliqué avec l'introduction du multithreading préemptif (c'est-à-dire que le système d'exploitation décide quand les threads doivent céder le contrôle), et c'est pourquoi la connexion n'est pas immédiatement claire aujourd'hui.

Donc, pour répondre à la dernière question: Oui, la machine d'état est une alternative, la plupart des interfaces graphiques fonctionnent de cette façon avec le thread GUI. Ne poussez pas trop loin la machine d'état, elle devient très difficile à maintenir très rapidement.

thiton
la source
0

Demander si l'utilisation d'une machine d'état est viable dans un langage de haut niveau revient un peu à se demander si l'écriture dans l'assembleur est une alternative viable à l'utilisation d'un langage de haut niveau. Ils ont tous les deux leur place, vu la bonne situation.

L'abstraction de l'utilisation du threading rend les systèmes parallèles plus complexes plus faciles à implémenter, mais finalement tous les systèmes parallèles ont les mêmes problèmes à traiter. Les problèmes classiques comme Deadlock / Livelock et l'inversion de priorité sont tout aussi possibles avec des systèmes basés sur des machines à états qu'avec un système parallèle à mémoire partagée , NUMA ou même basé sur CSP , s'il est suffisamment complexe.

Mark Booth
la source
0

Je ne pense pas que ce soit le cas - bien sûr, les machines à états sont un concept informatique très «élégant» mais comme vous le dites, elles sont assez compliquées. Et les choses compliquées sont difficiles à réussir. Et les choses qui ne vont pas sont juste cassées, donc à moins que vous ne soyez un génie de la stature présumée d'Alan Cox, restez avec des choses que vous savez fonctionner - laissez le `` codage intelligent '' aux projets d'apprentissage.

Vous pouvez dire quand quelqu'un a tenté en vain d'en faire un, car (en supposant que cela fonctionne bien) quand il s'agit de le maintenir, vous trouvez que la tâche est presque impossible. Le `` génie '' d'origine aura évolué en vous laissant avec le morceau de code à peine compréhensible (car ce type de développeurs n'a pas tendance à laisser trop de commentaires, sans parler de la documentation technique).

Dans certains cas, une machine d'état sera un meilleur choix - je pense à des trucs de type intégré maintenant où certains modèles de machine d'état sont utilisés, et utilisés de manière répétée et de manière plus formalisée (c'est-à-dire une ingénierie appropriée :))

Le threading peut également être difficile à réaliser correctement, mais il existe des modèles pour vous aider - principalement en réduisant le besoin de partager des données entre les threads.

Le dernier point à ce sujet est que les ordinateurs modernes fonctionnent de toute façon sur de nombreux cœurs, donc une machine d'état ne tirera pas vraiment parti des ressources disponibles. Le filetage peut faire un meilleur travail ici.

gbjbaanb
la source
2
Les machines à états ne sont pas du tout compliquées! Les machines à états complexes sont compliquées, mais tous les systèmes complexes aussi: o)
MaR
2
-1 pour "ne pas essayer". c'est le pire conseil que vous puissiez donner.
Javier
1
-1 "N'essaye pas"? C'est juste stupide. Je contesterais également votre affirmation selon laquelle les machines d'état sont difficiles. Une fois que vous entrez dans quelque chose comme une machine à états floue Heirarchal ... alors oui, c'est un peu plus compliqué. Mais une simple machine à états? C'est des trucs assez basiques que tous les 2 ans apprennent quand j'étais à l'école.
Steven Evers
permettez-moi de reformuler le bit «ne pas essayer» ...
gbjbaanb
0

Bon exemple d'une utilisation correcte de la machine d'état au lieu des threads: nginx vs apache2. En général, vous pouvez supposer que nginx gère toutes les connexions dans un thread, apache2 crée un thread par connexion.

Mais pour moi, utiliser des machines d'état vs des threads est assez similaire en utilisant asm vs java parfaitement conçu à la main: vous pouvez obtenir des résultats incroyables, mais cela prend beaucoup d'efforts de programmeurs, beaucoup de discipline, rend le projet plus complexe et ne vaut que lorsqu'il est utilisé par beaucoup d'autres programmeurs. Donc, si vous êtes celui qui veut créer un serveur Web rapide - utilisez des machines d'état et des IO asynchrones. Si vous écrivez le projet (pas la bibliothèque à utiliser partout) - utilisez des threads.

ZeusTheTrueGod
la source