Questions générales : while(1) et mutex

Réalisation d'un ordonnanceur

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous,

Je suis actuellement en train de développer un petit ordonnanceur ( très simplifié) en C++, sous environnement Eclipse, compilation sous Linux, et je me pose quelques questions d'ordre général que je vous soumets donc, si vous le voulez bien.

1) A quoi servent les boucles while(1) ? Dans pas mal d'exemple de multithreading trouvés sur internet, le dev utilise de telles boucles dans ses threads, mais je n'en vois pas vraiment l'utilité puisqu'une boucle infinie, en théorie, c'est "mal" , non ?

2) Qu'est ce qu'un mutex, je veux dire en pratique ? C'est une sorte de sémaphore, ou alors les sémaphores sont-ils une sorte de mutex ? Il me semble qu'un mutex délivre une instruction au processeur, non ? Je n'arrive pas bien à saisir tous les concepts derrière la notion de thread et ça me bloque un peu pour avancer.

3) J'ai toujours entendu " mettre un using namespace std c'est pas bien" ( contrairement à ce qu'on fait en cours ^^") et ça je crois que c'est pour éviter des collisions si on utilise deux espace contenant des fonctions homonymes, mais j'entends aussi " tout faire dans le main c'est pas bien, il faut utiliser des classes " ( comme ce qu'on fait en cours par contre ) mais ici je ne vois pas comment faire une classe, du coup j'ai déclaré mes thread dans le main, mais j'aimerai savoir s'il existait une solution plus " propre".

4) J'aimerai pour finir savoir s'il existait une réelle différence entre int main() et int main(void). il me semble avoir lu que l'un permettait de surcharger la fonction avec des paramètres et que l'autre non ?

Je joins mon code, si des gens sont intéressés ou ont des commentaire sur la propreté ou l'efficacité ( je débute donc j'ai un peu de mal avec toutes les notions pour l'instant)

Merci beaucoup à ceux qui ont prit le temps de lire, et merci encore à ceux qui prendront le temps de m'éclairer.

Bonne journée !

Includes et déclarations

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <pthread.h>
#include <limits>
#include <iostream>
#include <string>
#include <cstdlib>
#include <fstream>


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // on déclare un mutex
pthread_cond_t condition = PTHREAD_COND_INITIALIZER; // on déclare la condition

Mon premier thread :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void *execution(void *arg)
{
  int nbThread=0;
  std::string nom;

  std::ifstream file( "Parametrage_Job.txt" ); // lire le fichier de l'utilisateur

  if (file) // echec si le fichier n'est pas présent dans le répertoire
  {
      int lignes = 0;


      while (file.ignore(std::numeric_limits <int>::max(), '\n' ) )// 1 ligne = 1 thread : on cherche le nombre de jobs dans le fichier
      {
       ++lignes;
      }

      nbThread=lignes;
  }


  for(int i=0;i<=nbThread;i++)
  {
      std::string nom;

      while ( getline( file, nom))
      {
          std::cout << "Tache " << i << ": " << nom << std::endl; // Problème : rien ne s'affiche sur la console

      }


      if (i > nbThread )
      {
          pthread_mutex_lock (&mutex); /* On verrouille le mutex */
          pthread_cond_signal (&condition); /* On délivre le signal : condition remplie */
          pthread_mutex_unlock (&mutex); /* On déverrouille le mutex */

          nbThread=0;
      }

  }

  (void) arg;
  pthread_exit(NULL);
 }

Mon deuxième thread

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void *confirmation(void *arg)
{

  pthread_mutex_lock (&mutex); /* On verrouille le mutex */
  pthread_cond_wait( &condition, &mutex); // attendre une condition

  std::cout << " Toutes vos taches se sont accomplies" << std::endl;

  pthread_mutex_unlock (&mutex); /* On déverrouille le mutex */

  pthread_exit(NULL);
}

Mon main

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int main(void)
{

  pthread_t ThreadTache;
  pthread_t ThreadVerification;

  pthread_create(&ThreadTache, NULL,execution,NULL);
  pthread_create(&ThreadVerification, NULL, confirmation,NULL);

  pthread_join (ThreadTache,NULL);
  pthread_join (ThreadVerification,NULL);

return 0;}

+0 -0
  • Pourquoi while(1)? : le principe est de dire "fais cette suite d'action indéfiniement jusqu'à ce qu'on te demande explicitement de t'arrêter. C'est le seul cas où la boucle infinie est désirée.
  • Mutex ça veut dire quoi? : le mot "mutex" est un mot valise pour "mutuellement exclusif". L'idée derrière ça c'est imaginer une partie de jungle speed. Le totem est au milieu de la table. Mais seul un joueur peut l'attraper. Si deux essaie de l'avoir en même temps, le second va devoir attendre son tour. En ce sens, les sémaphore initialisées à 1 sont des mutex, en effet.
  • unising std : c'est pratique uniquement si tu maîtrises ce que tu fais. En effet en faisant using std tu importes tous les noms, même ceux dont tu n'as pas besoin, du coup tu peux avoir des collisions. Si en cours on te propose de le faire, c'est surtout parce que les exercices sont par définition des choses maîtrisées par ton prof. Du coup lui il veut te retirer les parties inutilement complexes pour retirer de l'exo la notion qu'il veut t'apprendre. Dans la vie réelle, c'est pas du tout comme ça, c'est pour ça qu'on dit que faire ce genre de chose, c'est pas une bonne pratique.
  • mettre dans le main, faire des classes, des fonctions? : il ne faut pas tout confondre. Tout mettre dans le main c'est une mauvaise pratique car ça empêche de voir du premier coup d'oeil à quel type de programme on a affaire (daemon, one shot, listener…) et en plus ça empêche d'avoir plusieurs main. Faire des classes, ça implique de faire de la POO et là on est dans un domaine bien plus complexe qui sort complètement du cadre. Ici, typiquement, on aurait voulu avoir un fichier "threads_utils.cpp/.h" par exemple où sont définies ces fonctions. En plus on aurait très sûrement préféré du templating que du void*.

Lu'!

En fait ce n'est plus vraiment le genre de code que l'on veut écrire en C++ et depuis un certain temps déjà ;) . D'une part parce que boost nous permettait déjà d'avoir quelque chose de plus C++-ien. Et surtout parce qu'aujourd'hui c'est la bibliothèque standard qui nous le permet.

Le problème : C++ est un langage à exceptions, et on ne manipule pas les ressources manuellement dans ce genre de contexte à moins d'avoir une excellent raison de le faire (l'article sur le RAII parce qu'en fait le RAII ce n'est pas que la mémoire).

Prenons par exemple ce bout de code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void *confirmation(void *arg)
{

  pthread_mutex_lock (&mutex); /* On verrouille le mutex */
  pthread_cond_wait( &condition, &mutex); // attendre une condition

  std::cout << " Toutes vos taches se sont accomplies" << std::endl;

  pthread_mutex_unlock (&mutex); /* On déverrouille le mutex */

  pthread_exit(NULL);
}

Formellement, il crée un deadlock ! Pourquoi ? Parce que l'opérateur ostream& operator<<(...), d'après la documentation, est susceptible de jeter une exception. Donc, ta ligne std::cout<<"bla"<<std::endl peut jeter une exception (en pratique, ça n'arriverait probablement jamais). Quel est le résultat ? Si l'exception est jetée, à ce point de programme tu as une rupture du flot d'exécution et le "unlock" ne serait jamais appelé.

Pouf, c'est tout cassé.

La solution à ça, c'est d'exploiter le RAII. Et en C++, cela se faisait déjà avec boost, mais ça se fait surtout à travers le support des threads en C++, avec std::mutex, std::unique_lock/std::lock_guard, std::condition_variable, etc.

Et par extension :

[…] Ici, typiquement, on aurait voulu avoir un fichier "threads_utils.cpp/.h" par exemple où sont définies ces fonctions. En plus on aurait très sûrement préféré du templating que du void*.

artragis

On fera encore mieux en exploitant std::thread, qui nous permettra de nous débarrasser de ces void* tous cracra :) .

J'aimerai pour finir savoir s'il existait une réelle différence entre int main() et int main(void). il me semble avoir lu que l'un permettait de surcharger la fonction avec des paramètres et que l'autre non ?

Paack

Signatures de main en C++.

(grillé)

Je rajouterai que les mutex, c'est un peu la solution du pauvre. Il est des patterns bien mieux pour échanger des informations. A partir de 2 mutex, on a vite fait de se retrouvés bloqués si on n'est pas bien organisés (hiérarchiquement).

Aussi, plutôt que pthread, je t'invite à utiliser le standard du C++11, ou au pire boost.thread. C'est bien plus C++ friendly que pthread qui ne t'offre rien pour assurer une libération de mutex même en cas d'exception ou autre retour prématuré.

4- En C, f() est équivalent à f(...). En C++ f() est équivalent à f(void). Quant à main(), il doit retourner un int, puis soit ne pas prendre d'argument, soit prendre un int plus un char**.

En fait ce n'est plus vraiment le genre de code que l'on veut écrire en C++ et depuis un certain temps déjà ;)

Je me doute bien mais c'est mon sujet de stage ouvrier donc c'est surtout pour apprendre je pense.

Merci de vos réponses, j'ai bien mieux compris les notions qui me posaient problème ( while(1), les espaces nommés ) et un peu mieux cerné le pourquoi des différences entre mes TD/projets de cours et de vrais projets.

Je vais créer les fichiers .h/.cpp et regardé comment remplacer mes void* mais juste une question supplémentaire .. pourquoi c'est tout cracra ? ^^"

Merci de vos réponses et des liens, je vais lire tout ça et essayer d'améliorer mon code !

EDIT suite à la réponse de lmghs :

je t'invite à utiliser le standard du C++11

… Je n'ai pas compris ^^". J'étais en effet tombé sur boost.thread lors de mes recherches pré-projet mais j'ai tellement galéré à installer Eclipse, MinGW et à importer toutes les bibliothèques que je n'ai pas osé me lancer dedans. Mais il semble qu'il soit temps de remonter les manches, je vais voir tout ça merci beaucoup !

+0 -0

Je vais créer les fichiers .h/.cpp et regardé comment remplacer mes void* mais juste une question supplémentaire .. pourquoi c'est tout cracra ? ^^"

Paack

Si tu n'utilises que les éléments de posix, tu ne pourras pas complètement te débarrasser des void car C ne te permettra pas de faire autrement pour avoir du code "générique". Le problème de void, c'est que ce n'est pas typé du tout, cela passera donc systématiquement par des casts violents, ce qui pour l'évolutivité et la maintenance est un nid à catastrophes nucléaires.

Si tu n'utilises que les éléments de posix, tu ne pourras pas complètement te débarrasser des void car C ne te permettra pas de faire autrement pour avoir du code "générique". Le problème de void, c'est que ce n'est pas typé du tout, cela passera donc systématiquement par des casts violents, ce qui pour l'évolutivité et la maintenance est un nid à catastrophes nucléaires.

J'avoue ne pas avoir tout bien compris, mais je vais chercher plus en avant dans tout ça.

Merci beaucoup de toutes vos réponses !

En fait ce n'est plus vraiment le genre de code que l'on veut écrire en C++ et depuis un certain temps déjà ;)

Je me doute bien mais c'est mon sujet de stage ouvrier donc c'est surtout pour apprendre je pense. […] Je vais créer les fichiers .h/.cpp et regardé comment remplacer mes void* mais juste une question supplémentaire .. pourquoi c'est tout cracra ? ^^"

Parce que la première fois, ton void* tu sauras qu'en fait c'est un int, et puis un jour tu voudras en faire un double et peut-être même jongler avec autre chose et que tu perdras un temps pas possible à changer les types des choses que tu passes d'un côté et que tu reçois d'un autre côté, et ce sans la moindre aide du compilateur. Quand il y a un vrai typage, le compilateur nous dit que l'on cherche à faire des choses qui ne sont pas ce que l'on veut vraiment faire. Avec des void* on est seul à poil dans la plaine.

EDIT suite à la réponse de lmghs :

je t'invite à utiliser le standard du C++11

… Je n'ai pas compris ^^". J'étais en effet tombé sur boost.thread lors de mes recherches pré-projet mais j'ai tellement galéré à installer Eclipse, MinGW et à importer toutes les bibliothèques que je n'ai pas osé me lancer dedans. Mais il semble qu'il soit temps de remonter les manches, je vais voir tout ça merci beaucoup !

Paack

Il y a 5 ans de cela, un nouveau standard C++ est sorti. Depuis, il existe officiellement un support pour le threading en C++. C'est ce que Ksass`Peuk t'as décrit en te parlant de std::trhead & cie.

Maintenant, je ne sais pas où son support en est côté mingw ou cygwin. Avec VC++, ça sera mieux si tu es sous windows. Au pire, il y a boost. Et si tu le veux pour gcc sous windows, va les cherches tous les deux à cette adresse: http://nuwen.net/mingw.html

Si tu dois faire évoluer un programme, tu n'auras probablement pas le choix et utiliser ce qui est déjà en place. Si tu pars de rien, je te conseille de partir dans une direction pérenne. Et si c'est pour apprendre. Justement, apprends à manipuler des outils d'avenir si tu dois faire du C++.

En ce sens, les sémaphore initialisées à 1 sont des mutex, en effet.

Notons aussi que la difference essentielle entre une semaphore et un mutex c'est la notion d'ownership. N'importe quel processus peut ajouter ou retirer un jeton pour une semaphore, alors que pour un mutex, c'est celui qui prend le lock qui doit le rendre.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte