Aux débuts de FORTRAN et de BASIC, pratiquement tous les programmes étaient écrits avec des déclarations GOTO. Le résultat était un code spaghetti et la solution était une programmation structurée.
De même, les pointeurs peuvent avoir des caractéristiques difficiles à contrôler dans nos programmes. C ++ a commencé avec beaucoup de pointeurs, mais l'utilisation de références est recommandée. Des bibliothèques telles que STL peuvent réduire certaines de nos dépendances. Il existe également des idiomes pour créer des pointeurs intelligents présentant de meilleures caractéristiques, et certaines versions de C ++ permettent des références et du code géré.
Les pratiques de programmation telles que l'héritage et le polymorphisme utilisent beaucoup de pointeurs dans les coulisses (comme pour la programmation structurée, le code est rempli d'instructions de branche). Des langages tels que Java éliminent les pointeurs et utilisent la récupération de place pour gérer les données allouées dynamiquement au lieu de dépendre des programmeurs pour qu'ils correspondent à leurs nouvelles instructions et à leurs instructions delete.
Dans ma lecture, j'ai vu des exemples de programmation multi-processus et multi-threads qui ne semblent pas utiliser de sémaphores. Utilisent-ils la même chose avec des noms différents ou disposent-ils de nouvelles méthodes pour structurer la protection des ressources contre les utilisations simultanées?
Par exemple, OpenMP est un exemple spécifique de système de programmation multithread avec des processeurs multicœurs. Il représente une région critique comme suit, sans l'utilisation de sémaphores, qui semblent ne pas être inclus dans l'environnement.
th_id = omp_get_thread_num();
#pragma omp critical
{
cout << "Hello World from thread " << th_id << '\n';
}
Cet exemple est un extrait de: http://en.wikipedia.org/wiki/OpenMP
Sinon, une protection similaire des threads utilisant des sémaphores avec les fonctions wait () et signal () pourrait ressembler à ceci:
wait(sem);
th_id = get_thread_num();
cout << "Hello World from thread " << th_id << '\n';
signal(sem);
Dans cet exemple, les choses sont assez simples, et une simple revue suffit pour montrer que les appels wait () et signal () sont appariés et même avec beaucoup de simultanéité, la sécurité des threads est assurée. Mais d'autres algorithmes sont plus compliqués et utilisent plusieurs sémaphores (binaires et compteurs) répartis sur plusieurs fonctions avec des conditions complexes pouvant être appelées par de nombreux threads. Il peut être difficile de gérer les conséquences d’une impasse ou de l’absence de sécurité.
Est-ce que ces systèmes comme OpenMP éliminent les problèmes de sémaphores?
Est-ce qu'ils déplacent le problème ailleurs?
Comment transformer mon sémaphore préféré en utilisant un algorithme pour ne plus utiliser de sémaphores?
la source
Réponses:
Existe-t-il des techniques et des pratiques de programmation simultanées qu’on ne devrait plus utiliser? Je dirais oui .
Une des premières techniques de programmation simultanée qui semble rare de nos jours est la programmation par interruptions . C’est ainsi que fonctionnait UNIX dans les années 1970. Voir le Commentaire Lions sur UNIX ou la conception du système d'exploitation UNIX par Bach . En bref, la technique consiste à suspendre temporairement les interruptions lors de la manipulation d'une structure de données, puis à restaurer les interruptions par la suite. La page de manuel BSD spl (9)a un exemple de ce style de codage. Notez que les interruptions sont axées sur le matériel et que le code incarne une relation implicite entre le type d'interruption matérielle et les structures de données associées à ce matériel. Par exemple, le code manipulant les tampons d'E / S de disque doit suspendre les interruptions du matériel du contrôleur de disque lorsqu'il travaille avec ces tampons.
Ce style de programmation était utilisé par les systèmes d'exploitation sur du matériel à un seul processeur. Il était beaucoup plus rare que les applications traitent des interruptions. Certains systèmes d'exploitation avaient des interruptions logicielles, et je pense que les gens ont essayé de construire des systèmes de threading ou de coroutine dessus, mais cela n'était pas très répandu. (Certainement pas dans le monde UNIX.) Je soupçonne que la programmation de type interruption est aujourd'hui limitée aux petits systèmes embarqués ou aux systèmes temps réel.
Les sémaphores constituent un progrès par rapport aux interruptions car ce sont des constructions logicielles (non liées au matériel), ils fournissent des abstractions sur des installations matérielles et permettent le multithreading et le multitraitement. Le problème principal est qu’ils ne sont pas structurés. Le programmeur est responsable du maintien de la relation entre chaque sémaphore et les structures de données qu'il protège, de manière globale pour l'ensemble du programme. Pour cette raison, je pense que les sémaphores nus sont rarement utilisés aujourd'hui.
Un autre petit pas en avant est un moniteur , qui encapsule les mécanismes de contrôle de la concurrence (verrous et conditions) avec les données protégées. Cela a été reporté dans le système Mesa (lien alternatif) et de là dans Java. (Si vous lisez ce document Mesa, vous verrez que les verrous et les conditions du moniteur Java sont copiés presque intégralement de Mesa.) Les moniteurs sont utiles car un programmeur suffisamment prudent et diligent peut écrire des programmes concurrents en toute sécurité en utilisant uniquement un raisonnement local sur le code et les données. dans le moniteur.
Il existe d'autres structures de bibliothèque, telles que celles du
java.util.concurrent
package Java , qui incluent une variété de structures de données hautement concurrentes et de structures de regroupement de threads. Celles-ci peuvent être combinées à des techniques supplémentaires telles que le confinement du fil et l'immutabilité effective. Voir Java Concurrency In Practice de Goetz et. Al. pour plus de discussion. Malheureusement, de nombreux programmeurs continuent de lancer leurs propres structures de données avec des verrous et des conditions, alors qu'ils devraient vraiment utiliser quelque chose comme ConcurrentHashMap où les auteurs de la bibliothèque ont déjà fait le gros du travail.Tout ce qui précède partage certaines caractéristiques significatives: ils ont plusieurs threads de contrôle qui interagissent sur un état mutable et partagé dans le monde entier . Le problème est que la programmation dans ce style est toujours très sujette aux erreurs. Il est assez facile pour une petite erreur de passer inaperçue, ce qui entraîne une mauvaise conduite difficile à reproduire et à diagnostiquer. Il se peut qu'aucun programmeur ne soit "suffisamment prudent et diligent" pour développer de grands systèmes de cette manière. Au moins, très peu le sont. Donc, je dirais que la programmation multi-thread avec un état mutable et mutable devrait être évitée dans la mesure du possible.
Malheureusement, on ne sait pas très bien si cela peut être évité dans tous les cas. Beaucoup de programmation est encore faite de cette façon. Ce serait bien de voir cela supplanté par autre chose. Les réponses de Jarrod Roberson et davidk01 indiquent des techniques telles que les données immuables, la programmation fonctionnelle, le STM et la transmission de messages. Il y a beaucoup à leur recommander, et tous sont activement développés. Mais je ne pense pas qu'ils aient complètement remplacé le bon état mutable à l'ancienne et partagé pour l'instant.
EDIT: voici ma réponse aux questions spécifiques à la fin.
Je ne connais pas grand chose à propos d'OpenMP. Mon impression est que cela peut être très efficace pour des problèmes très parallèles tels que des simulations numériques. Mais cela ne semble pas être d'usage général. Les constructions de sémaphore semblent de bas niveau et obligent le programmeur à maintenir la relation entre les sémaphores et les structures de données partagées, avec tous les problèmes que j'ai décrits ci-dessus.
Si vous avez un algorithme parallèle qui utilise des sémaphores, je ne connais aucune technique générale pour le transformer. Vous pourrez peut-être le transformer en objets, puis construire des abstractions autour de celui-ci. Mais si vous voulez utiliser quelque chose comme la transmission de messages, je pense que vous devez vraiment reconceptualiser l’ensemble du problème.
la source
Réponse à la question
Le consensus général est que l' état mutable partagé est Bad ™ et que l'état immuable est Good ™, ce qui prouve son exactitude et sa véracité encore et encore par les langages fonctionnels et les langages impératifs.
Le problème, c’est que les langues impératives ordinaires ne sont tout simplement pas conçues pour gérer cette façon de travailler, les choses ne vont pas changer pour ces langues du jour au lendemain. C'est là que la comparaison
GOTO
est imparfaite. L'état immuable et la transmission de messages sont une excellente solution, mais ce n'est pas non plus une panacée.Locaux imparfaits
Cette question est basée sur des comparaisons avec une prémisse erronée; qui
GOTO
était le problème réel et a été universellement dépréciée d' une façon par les syndicats Intergalatic Conseil universelle des langues concepteurs et génie logiciel ©! SansGOTO
mécanisme, l'ASM ne fonctionnerait pas du tout. Idem avec le principe que les pointeurs bruts sont le problème avec C ou C ++ et que certains pointeurs intelligents sont une panacée, ils ne le sont pas.GOTO
Ce n'était pas le problème, ce sont les programmeurs. Même chose pour l' état mutable partagé . En soi, ce n'est pas le problème , ce sont les programmeurs qui l'utilisent qui pose problème. S'il existait un moyen de générer du code utilisant un état mutable partagé de manière à ce qu'il n'y ait jamais eu de problème de concurrence ou de bogue, cela ne poserait pas de problème. C'est un peu comme si vousGOTO
n'écriviez jamais de code spaghetti avec ou des constructions équivalentes, ce n'était pas un problème non plus.L'éducation est la panacée
Les programmeurs sont idiotes ce sont
deprecated
, chaque langue populaire a toujours laGOTO
construction soit directement ou indirectement , et il est unbest practice
si correctement utilisé dans toutes les langues qui a ce type de constructions.Exemple: Java a des étiquettes et les
try/catch/finally
deux qui travaillent directement comme desGOTO
déclarations.La plupart des programmeurs Java à qui je parle ne savent même pas ce que cela
immutable
signifie réellement en dehors d'eux répétantthe String class is immutable
avec un zombie comme le regard dans les yeux. Ils ne savent vraiment pas comment utiliser lefinal
mot clé correctement pour créer uneimmutable
classe. Je suis donc presque sûr qu'ils ne savent pas pourquoi la transmission de messages à l' aide de messages immuables est si géniale et pourquoi l'état mutable partagé n'est pas si génial.la source
La dernière en date dans les milieux universitaires semble être la mémoire logicielle transactionnelle (STM), qui promet de supprimer tous les détails complexes de la programmation multithread des programmeurs en utilisant une technologie de compilateur suffisamment intelligente. En coulisse, il y a toujours des verrous et des sémaphores, mais vous en tant que programmeur n'avez pas à vous en préoccuper. Les avantages de cette approche ne sont toujours pas clairs et il n'y a pas de candidats évidents.
Erlang utilise des agents de transmission de messages et des agents pour l'accès simultané. Il s'agit d'un modèle plus simple à utiliser que STM. Avec la transmission de messages, vous n'avez absolument aucun souci à vous poser sur les verrous et les sémaphores, car chaque agent fonctionne dans son propre mini-univers. Il n'y a donc pas de conditions de concurrence liées aux données. Vous avez encore quelques cas étranges, mais ils sont loin d'être aussi compliqués que les vivres et les impasses. Les langues de la machine virtuelle Java peuvent utiliser Akka et bénéficier de tous les avantages de la transmission de messages et d'acteurs. Contrairement à Erlang, la machine virtuelle Java ne prend pas en charge les acteurs. A la fin de la journée, Akka utilise toujours des threads et des verrous le programmeur n'a pas à s'en soucier.
L’autre modèle que je connais et qui n’utilise pas de verrous et de threads est l’utilisation de futures, qui est en réalité une autre forme de programmation async.
Je ne sais pas dans quelle mesure cette technologie est disponible en C ++, mais il est probable que si vous voyez quelque chose qui n'utilise pas explicitement les threads et les verrous, il s'agira d'une des techniques ci-dessus pour gérer les accès simultanés.
la source
Je pense que cela concerne principalement les niveaux d'abstraction. Très souvent, en programmation, il est utile d’abréger certains détails de manière plus sûre ou plus lisible ou quelque chose du genre.
Ceci s'applique aux structures de contrôle:
if
s,for
s et pairstry
- lescatch
blocs ne sont que des abstractions degoto
s. Ces abstractions sont presque toujours utiles, car elles rendent votre code plus lisible. Mais il y a des cas où vous aurez toujours besoin d'utilisergoto
(par exemple, si vous écrivez l'assemblage à la main).Cela s'applique également à la gestion de la mémoire: les pointeurs intelligents C ++ et GC sont des abstractions sur des pointeurs bruts et une dés / allocation de mémoire manuelle. Et parfois, ces abstractions ne sont pas appropriées, par exemple lorsque vous avez vraiment besoin de performances maximales.
Et la même chose s'applique au multi-threading: des choses comme les futurs et les acteurs ne sont que des abstractions sur des threads, des sémaphores, des mutexes et des instructions CAS. De telles abstractions peuvent vous aider à rendre votre code beaucoup plus lisible et à éviter les erreurs. Mais parfois, ils ne sont tout simplement pas appropriés.
Vous devriez savoir quels outils vous avez à votre disposition et quels sont leurs avantages et leurs inconvénients. Ensuite, vous pouvez choisir l'abstraction correcte pour votre tâche (le cas échéant). Des niveaux d'abstraction plus élevés ne déprécient pas les niveaux plus bas, il y aura toujours des cas où l'abstraction n'est pas appropriée et le meilleur choix consiste à utiliser la méthode «ancienne».
la source
Oui, mais vous ne rencontrerez probablement pas certains d'entre eux.
À l’époque, il était courant d’utiliser des méthodes de blocage (synchronisation de barrière) car il était difficile d’écrire de bons mutex. Vous pouvez toujours voir des traces de cela dans les choses récentes. L'utilisation de bibliothèques de concurrence simultanées vous donne un ensemble d'outils beaucoup plus riches et testés pour la parallélisation et la coordination interprocessus.
De même, une pratique plus ancienne consistait à écrire du code tortueux de manière à ce que vous puissiez trouver un moyen de le mettre en parallèle manuellement. Cette forme d’optimisation (potentiellement nuisible, si vous vous trompez) a également largement disparu avec l’avènement des compilateurs qui le font pour vous, défilant les boucles si nécessaire, suivant les branches de manière prédictive, etc. Ce n’est cependant pas une nouvelle technologie. , étant au moins 15 ans sur le marché. Profiter de choses comme les pools de threads permet également de contourner certains codes très astucieux d’antan.
Donc, la pratique obsolète est peut-être d’écrire vous-même le code de concurrence, au lieu d’utiliser des bibliothèques modernes et bien testées.
la source
Grand Central Dispatch d'Apple est une abstraction élégante qui a changé ma vision de la concurrence. L’accent mis sur les files d’attente facilite la mise en oeuvre de la logique asynchrone, selon mon humble expérience.
Lorsque je programme dans des environnements où cela est disponible, cela a remplacé la plupart de mes utilisations de threads, de verrous et de communication inter-thread.
la source
L'un des principaux changements apportés à la programmation parallèle réside dans le fait que les processeurs sont considérablement plus rapides qu'auparavant, mais pour obtenir ces performances, un cache bien rempli est nécessaire. Si vous essayez d'exécuter plusieurs threads en même temps en les échangeant continuellement, vous allez presque toujours invalider le cache pour chaque thread (c'est-à-dire que chaque thread nécessite des données différentes pour fonctionner) et vous finissez par nuire beaucoup plus aux performances que vous le souhaitez. utilisé avec des processeurs plus lents.
C’est une des raisons pour lesquelles les frameworks asynchrones ou basés sur des tâches (par exemple Grand Central Dispatch ou Intel TBB) sont plus populaires, ils exécutent le code 1 tâche à la fois, en l’achevant avant de passer à la suivante. Cependant, vous devez coder chaque tâche. chaque tâche prend peu de temps à moins que vous ne vouliez visser la conception (c’est-à-dire que vos tâches parallèles sont vraiment mises en file d’attente). Les tâches gourmandes en ressources processeur sont transmises à un autre cœur de processeur au lieu d'être traitées sur un seul thread traitant toutes les tâches. Il est également plus facile à gérer s’il n’ya pas de traitement réellement multithread.
la source