213 votes

Mutex exemple / tutoriel?

Je suis nouveau sur le multithreading, et a été à essayer de comprendre comment les mutex travail. Fait beaucoup de recherches sur Google et j'ai trouvé un tutoriel décent, mais il reste encore quelques doutes de comment cela fonctionne, car j'ai créé mon propre programme dans lequel le verrouillage ne fonctionne pas.

Un absolument non-intuitive syntaxe de le mutex est - pthread_mutex_lock( &mutex1 );, où il semble que le mutex est verrouillé, quand ce que je veux vraiment lock est une autre variable. Cette syntaxe dire que de verrouiller un mutex verrouille une région de code jusqu'à ce que le mutex est débloqué? Alors comment voulez-fils savent que la région est-il verrouillé? [Mise à JOUR: Threads savoir que la région est verrouillé, par la Mémoire de l'Escrime ]. Et ce n'est pas un phénomène censé être appelée section critique? [Mise à JOUR: Critique de la section objets sont disponibles uniquement sous Windows, où les objets sont plus rapides que les mutex et sont visibles que par le thread qui l'implémente. Sinon, la section critique, renvoie à la zone de code protégée par un mutex]

En bref, pourriez-vous s'il vous plaît aider avec le plus simple possible mutex exemple de programme et la plus simple possible explication sur la logique de la façon dont il fonctionne? Je suis sûr que cela va aider beaucoup d'autres débutants.

356voto

Nav Points 3105

Modifications à l'explication et le code sont la plupart de bienvenue (pour le rendre plus clair/plus court/simple/plus correct).
Voici mon humble tentative d'expliquer le concept aux débutants dans le monde: (un code couleur de la version sur mon blog aussi)

Beaucoup de gens courent pour une seule cabine téléphonique (non mobiles) pour parler à leurs proches. La première personne à attraper le bouton de la porte de la cabine, est le seul qui est autorisé à utiliser le téléphone. Il a à retenir de la poignée de la porte tant qu'il utilise le téléphone, sinon quelqu'un d'autre va prendre la main sur la poignée, le jeter hors de et parler à sa femme :) Il n'y a pas de système de file d'attente en tant que tel. Lorsque la personne a terminé son appel, qui vient de sortir de la cabine et les feuilles de la poignée de la porte, la personne suivante pour obtenir la main de la poignée de la porte seront autorisés à utiliser le téléphone.

Un thread est : Chaque personne
Le mutex est : la poignée de La porte
Le verrouillage est : la main de La personne
La ressource est : Le téléphone

N'importe quel thread qui doit exécuter quelques lignes de code qui ne devrait pas être modifiée par d'autres threads en même temps (en utilisant le téléphone pour parler à sa femme), a d'abord acquérir un verrou sur un mutex (en agrippant la poignée de la porte de la cabine). C'est alors seulement un thread être en mesure d'exécuter ces lignes de code (appel téléphonique).

Une fois que le thread a exécuté ce code, il doit libérer le verrou sur le mutex, de sorte qu'un autre thread puisse acquérir un verrou sur le mutex (d'autres personnes de pouvoir accéder à la cabine téléphonique).

[Le concept d'avoir un mutex est un peu absurde lorsque l'on considère le monde réel de l'accès exclusif, mais dans le monde de la programmation, je pense qu'il y a pas d'autre moyen de laisser les autres threads "voir" qu'un fil était déjà pour l'exécution de certaines lignes de code. Il y a des concepts de récursive mutex etc, mais cet exemple est uniquement destiné à vous montrer le concept de base. Espérons que l'exemple vous donne une image claire de la notion.]

Avec C++11 filetage:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex m;//you can use std::lock_guard if you want to be exception safe
int i = 0;

void makeACallFromPhoneBooth() 
{
    m.lock();//man gets a hold of the phone booth door and locks it. The other men wait outside
      //man happily talks to his wife from now....
      std::cout << i << " Hello Wife" << std::endl;
      i++;//no other thread can access variable i until m.unlock() is called
      //...until now, with no interruption from other men
    m.unlock();//man leaves the phone booth
}

int main() 
{
    //This is the main crowd of people uninterested in making a phone call

    //man1 leaves the crowd to go to the phone booth
    std::thread man1(makeACallFromPhoneBooth);
    //Although man2 appears to start second, there's a good chance he might
    //reach the phone booth before man1
    std::thread man2(makeACallFromPhoneBooth);
    //And hey, man3 also joined the race to the booth
    std::thread man3(makeACallFromPhoneBooth);

    man1.join();//man1 finished his phone call and joins the crowd
    man2.join();//man2 finished his phone call and joins the crowd
    man3.join();//man3 finished his phone call and joins the crowd
    return 0;
}

Compiler et exécuter à l'aide d' g++ -std=c++0x -pthread -o thread thread.cpp;./thread

Avec TBB: Vous aurez besoin de TBB pour exécuter le programme ci-dessous, mais l'intention de poster TBB code est que vous comprenez la séquence de verrouillage et de déverrouillage juste en regardant le code simple (peut-ai montré l'étendue de verrouillage en n'utilisant pas d'acquérir et de libération - qui est aussi une exception sûr -, mais c'est plus clair).

#include <iostream>
#include "/tbb/mutex.h"
#include "/tbb/tbb_thread.h"
using namespace tbb;

typedef mutex myMutex;
static myMutex sm;
int i = 0;

void someFunction() 
{ 
      myMutex::scoped_lock lock;//create a lock
      lock.acquire(sm);//Method acquire waits until it can acquire a lock on the mutex
         //***only one thread can access the lines from here...***
         ++i;//incrementing i is safe (only one thread can execute the code in this scope) because the mutex locked above protects all lines of code until the lock release.
         sleep(1);//simply creating a delay to show that no other thread can increment i until release() is executed
         std::cout<<"In someFunction "<<i<<"\n";
         //***...to here***
      lock.release();//releases the lock (duh!)      
}

int main()
{
   tbb_thread my_thread1(someFunction);//create a thread which executes 'someFunction'
   tbb_thread my_thread2(someFunction);
   tbb_thread my_thread3(someFunction);

   my_thread1.join();//This command causes the main thread (which is the 'calling-thread' in this case) to wait until thread1 completes its task.
   my_thread2.join();
   my_thread3.join();
}

Notez que tbb_thread.h est obsolète. Le remplacement est indiqué ici.

52voto

San Jacinto Points 6109

Alors qu'un mutex peut être utilisé pour résoudre les autres problèmes, la principale raison d'exister est de fournir de l'exclusion mutuelle et ainsi de résoudre ce qui est connu comme une condition de course. Lorsque deux (ou plus) de threads ou processus tente d'accéder à la même variable simultanément, nous avons le potentiel pour une condition de course. Considérons le code suivant

//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
  i++;
}

Le fonctionnement interne de cette fonction à l'air si simple. C'est une seule instruction. Cependant, une pseudo-langage d'assemblage équivalent pourrait être:

load i from memory into a register
add 1 to i
store i back into memory

Parce que l'équivalent de langue de l'assembly instructions sont nécessaires pour effectuer l'opération d'incrémentation sur i, on dit que l'incrémentation de i est un non-atmoic opération. Une opération atomique est celui qui peut être effectuée sur le matériel avec une garantie de ne pas être interrompu une fois l'instruction, l'exécution a commencé. L'incrémentation de i est constitué d'une chaîne de 3 atomic instructions. Dans un tel système où plusieurs threads sont l'appel de la fonction, les problèmes surgissent quand un thread lit ou écrit au mauvais moment. Imaginons que nous ayons deux threads s'exécutant simultaneoulsy et on appelle la fonction immédiatement après l'autre. Disons aussi que nous avons, je initialisé à 0. Supposons également que nous avons beaucoup de registres et que les deux fils sont complètement à l'aide de différents registres, donc il n'y aura pas de collisions. Le calendrier de ces événements peuvent être:

thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1

Ce qui s'est passé, c'est que nous avons deux fils incrémentation je parallèlement, notre fonction est appelée deux fois, mais le résultat est en contradiction avec ce fait. Il ressemble à la fonction a été appelée seulement une fois. C'est parce que l'atomicité est "cassé" au niveau de la machine, sens les fils peuvent s'interrompre les uns les autres ou de travailler ensemble au mauvais moment.

Nous avons besoin d'un mécanisme pour résoudre ce problème. Nous avons besoin d'imposer certains de commande pour les instructions ci-dessus. Un mécanisme commun est de bloquer tous les fils sauf un. Pthread mutex utilise ce mécanisme.

N'importe quel thread qui doit exécuter quelques lignes de code qui peut mal modifier les valeurs partagées par d'autres threads en même temps (en utilisant le téléphone pour parler à sa femme), doit d'abord acquérir un verrou sur un mutex. De cette façon, n'importe quel thread qui nécessite l'accès aux données partagées doit passer par le mutex lock. C'est alors seulement un thread être en mesure d'exécuter le code. Cette section de code est appelé une section critique.

Une fois que le thread a été exécuté à la section critique, il doit libérer le verrou sur le mutex, de sorte qu'un autre thread puisse acquérir un verrou sur le mutex.

Le concept d'avoir un mutex semble un peu étrange lorsque l'on considère les humains qui cherchent un accès exclusif à de vrais objets physiques, mais lors de la programmation, nous devons être intentionnelle. Threads simultanés et les processus n'ont pas l'sociaux et culturels de l'éducation que nous, alors nous devons les forcer à partager des données bien.

Donc, techniquement parlant, comment un mutex travail? N'est-ce pas souffrir de la même race conditions que nous l'avons mentionné plus tôt? N'est-ce pas pthread_mutex_lock() un peu plus complexe qu'une simple incrémentation d'une variable?

Techniquement parlant, nous avons besoin d'un peu de matériel de soutien pour nous aider. Les concepteurs de matériel nous donner des instructions machine qui ne font plus qu'une chose mais qui sont garantis pour être atomique. Un exemple classique d'une telle instruction est le test-and-set (TAS). Lorsque vous essayez d'acquérir un verrou sur une ressource, on peut utiliser le TAS peut vérifier pour voir si une valeur en mémoire est de 0. Si elle l'est, que serait notre signal que la ressource est en cours d'utilisation et que nous ne faisons rien (ou, plus exactement, nous l'attendons par un mécanisme. Un pthreads mutex va nous mettre dans une file d'attente dans le système d'exploitation et de nous aviser lorsque la ressource est disponible. Dumber systèmes peut nous obliger à faire un serré spin boucle, les tests de la condition). Si la valeur dans la mémoire n'est pas 0, le TAS définit l'emplacement de quelque chose d'autre que 0 sans recourir à d'autres instructions. C'est comme la combinaison de deux instructions de montage en 1 pour nous donner l'atomicité. Ainsi, les essais et la modification de la valeur (en cas de changement est le cas) ne peut pas être interrompu une fois qu'il a commencé. Nous pouvons construire des mutex sur le dessus d'une telle instruction.

Remarque: certaines sections peuvent être similaires à ceux d'une précédente réponse. J'acceptai l'inviter à modifier, il a préféré l'original de la façon dont il a été, donc je vais garder ce que j'avais et qui est infusé avec un peu de son verbiage.

14voto

R.. Points 93718

Le tutoriel de fils meilleur que je connaisse est ici :

https://Computing.LLNL.gov/Tutorials/pthreads/

J’aime qu’il est écrit sur l’API, et non pas une implémentation particulière, et il donne quelques beaux exemples simples pour vous aider à comprendre la synchronisation.

10voto

fishfood Points 928

Je suis tombé sur ce post récemment et pense qu’il a besoin d’une solution de mise à jour de c ++11 mutex de la bibliothèque standard (c'est-à-dire std::mutex).

J’ai collé du code ci-dessous (mes premiers pas avec un mutex - j’ai appris d’accès concurrentiel sur win32 avec poignée, SetEvent, WaitForMultipleObjects etc.).

Puisque c’est ma première tentative avec std::mutex et amis, je serais ravi de voir les commentaires, suggestions et améliorations !

6voto

Howard Hinnant Points 59526

Les mutex sont ajoutés pour C++0x, et ce cours de C++, la meilleure façon de les utiliser n'est pas la même que dans d'autres langues. Ce n'est pas parce que le C++ est différent juste pour le plaisir d'être différent. C'est parce que le C++ a des destructeurs et des exceptions. Cela signifie que les suivantes:

void foo() 
{ 
      my_mutex.lock();
      // do stuff
      my_mutex.unlock();
}

est de savoir comment vous n'avez pas envie d'utiliser les mutex. Si quelque chose déclenche une exception entre my_mutex.lock() et my_mutex.unlock(), l'état verrouillé sur le mutex est une fuite (comme une fuite de mémoire, mais est susceptible de provoquer un véritable programme de blocage).

Voici un bref tutoriel sur le C++ mutex et comment les utiliser correctement. Ce lien contient également des informations sur le verrouillage partagé qui n'est pas standard C++. Ignorez tout avoir à faire avec la commune ou de la mise à niveau de verrouillage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X