Exemple Mutex / tutoriel?

je suis nouveau dans le multithreading, et j'essayais de comprendre comment les Mutex fonctionnent. A fait beaucoup de Googling et j'ai trouvé un tutoriel décent , mais il a laissé quelques doutes sur la façon dont cela fonctionne parce que j'ai créé mon propre programme dans lequel le verrouillage n'a pas fonctionné.

une syntaxe absolument non intuitive du mutex est pthread_mutex_lock( &mutex1 ); , où il ressemble au mutex est verrouillé, quand ce que je veux vraiment verrouiller est une autre variable. Cette syntaxe moyenne que verrouiller un mutex bloque une région de code jusqu'à ce que le mutex soit déverrouillé? Alors comment les threads savent-ils que la région est verrouillée? [ mise à JOUR: Threads savoir que la région est verrouillé, par la Mémoire de l'Escrime ]. Et un tel phénomène n'est-il pas censé s'appeler la section critique? [ mise à jour: les objets de la section critique sont disponibles dans Windows uniquement, où les objets sont plus rapides que les mutex et sont visibles seulement par le thread qui les implémente. Sinon, la section critique se réfère uniquement à 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 le plus simple possible explication sur la logique de comment cela fonctionne? Je suis sûr que cela aidera beaucoup d'autres débutants.

144
demandé sur Veedrac 2011-02-14 09:33:46

8 réponses

Voici mon humble tentative d'expliquer le concept à des débutants à travers le monde: (une version codée couleur 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 la poignée de porte de la cabine, est celui 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 saisir la poignée, le jeter dehors et parler à sa femme :) il n'y a pas de système de file d'attente en tant que tel. Lorsque la personne termine son appel, sort de la cabine et quitte la poignée de porte, la personne suivante pour mettre la main sur la poignée de porte sera autorisé à utiliser le téléphone.

Un thread "1519160920 est:" Chaque personne

Le mutex est: la poignée de porte

Le serrure est: la main de la personne

Le ressource est: le téléphone

tout thread qui doit exécuter certaines lignes de code qui ne doit pas être modifié par d'autres threads en même temps (en utilisant le téléphone pour parler à sa femme), doit d'abord acquérir une serrure sur un mutex (en serrant la poignée de 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 devrait libérer la serrure sur le mutex afin qu'un autre thread puisse acquérir une serrure sur le mutex (d'autres personnes pouvant accéder à la cabine téléphonique).

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

avec filetage C++11:

#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 lets go of the door handle and unlocks the door
}

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 en utilisant g++ -std=c++0x -pthread -o thread thread.cpp;./thread

au lieu d'utiliser explicitement lock et unlock , vous pouvez utiliser les parenthèses comme indiqué ici , si vous utilisez une serrure scopée pour l'avantage qu'elle fournit . Les écluses en pente ont une légère performance au-dessus.

avec TBB: Vous aurez besoin de TBB pour exécuter le programme ci-dessous, mais l'intention de l'affichage du code TBB est que vous comprenez la séquence de verrouillage et de déverrouillage juste en regardant le code simple (aurait pu montré le verrouillage scoped en n'utilisant pas acquérir et libérer - qui est également l'exception sûre -, 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() 
{ 
      //Note: Since a scoped lock is used below, you should know that you 
      //can specify a scope for the mutex using curly brackets, instead of 
      //using lock.acquire() and lock.release(). The lock will automatically 
      //get released when program control goes beyond the scope.
      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();
}

noter que tbb_thread.h est déprécié. Le remplacement est indiqué ici .

222
répondu Nav 2018-05-05 15:24:10

bien qu'un mutex puisse être utilisé pour résoudre d'autres problèmes, la raison première de leur existence est d'offrir l'exclusion mutuelle et donc de résoudre ce qui est connu comme une condition de race. Lorsque deux (ou plus) threads ou processus tentent d'accéder à la même variable simultanément, nous avons un 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++;
}

les internes de cette fonction semblent si simples. C'est une seule instruction. Cependant, un pseudo-ensemble typique l'équivalent linguistique pourrait être:

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

parce que les instructions équivalentes en langage d'assemblage sont toutes nécessaires pour effectuer l'opération d'incrément sur i, nous disons que l'incrément i est une opération non-atmoïque. 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é. Incrementing I se compose d'une chaîne de 3 instructions atomiques. Dans un système concurrent où plusieurs threads sont en appelant la fonction, des problèmes surgissent quand un thread lit ou écrit au mauvais moment. Imaginez que nous ayons deux threads fonctionnant simultanément et que l'un appelle la fonction immédiatement après l'autre. Disons aussi que nous avons initialisé à 0. Supposons aussi que nous avons beaucoup de registres et que les deux threads utilisent des registres complètement différents, 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 ont deux fils incrémentant Je concurremment, notre fonction est appelée deux fois, mais le résultat est incompatible avec ce fait. On dirait que la fonction n'a été appelée qu'une fois. Cela est dû au fait que l'atomicité est "cassée" au niveau de la machine, ce qui signifie que les fils peuvent s'interrompre ou travailler ensemble au mauvais moment.

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

tout thread qui doit exécuter certaines lignes de code qui peuvent 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 être fait acquérir une serrure sur un mutex. De cette façon, tout thread qui nécessite l'accès aux données partagées doit passer par la serrure mutex. Ce n'est qu'alors qu'un thread pourra exécuter le code. Cette section de code est appelée une section critique.

une fois que le thread a exécuté la section critique, il doit libérer la serrure sur le mutex afin qu'un autre thread puisse acquérir une serrure 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 à des objets physiques réels, mais lors de la programmation, nous devons être intentionnels. Les fils et les processus concurrents n'ont pas l'éducation sociale et culturelle que nous faisons, donc nous devons les forcer à partager les données gentiment.

donc techniquement parlant, comment fonctionne un mutex? Ne souffre-t-il pas des mêmes conditions de course que celles que nous avons mentionnées plus tôt? Pthread_mutex_lock() n'est-il pas un peu plus complexe qu'un simple incrément d'une variable?

techniquement parlant, nous avons besoin d'un support matériel pour nous aider. Les concepteurs du matériel nous donnent des instructions machine qui font plus d'une chose, mais qui sont garanties d'être atomiques. Un exemple classique d'une telle instruction est l' test-and-set (TAS). En essayant d'acquérir un verrou sur une ressource, nous pourrions utiliser le TAS pourrait vérifier pour voir si une valeur en mémoire est 0. Si c'est le cas, ce serait notre signal que la ressource est utilisée et que nous ne faisons rien (ou, plus précisément, que nous attendons selon un mécanisme quelconque). Un mutex pthreads nous mettra dans une file d'attente spéciale dans le système d'exploitation et nous avertira lorsque la ressource sera disponible. Dumber systems peut nous demander de faire une boucle de spin serrée, tester la condition encore et encore). Si la valeur en mémoire N'est pas 0, le TAS fixe l'emplacement à quelque chose d'autre que 0 sans utiliser d'autres instructions. C'est comme combiner deux instructions d'assemblage en 1 pour nous donner de l'atomicité. Ainsi, tester et changer la valeur (si Changer est approprié) ne peut pas être interrompu une fois qu'il a commencé. Nous pouvons construire des Mutex en plus d'une telle instruction.

Note: certaines sections peuvent ressembler à une réponse antérieure. J'ai accepté son invitation à éditer, il a préféré la façon originale était, donc je garde ce que j'ai eu qui est infusé avec un peu de son verbiage.

31
répondu San Jacinto 2011-03-01 20:56:28

le meilleur tutoriel de threads que je connaisse est ici:

https://computing.llnl.gov/tutorials/pthreads /

j'aime qu'il soit écrit sur L'API, plutôt que sur une implémentation particulière, et il donne quelques exemples simples pour vous aider à comprendre la synchronisation.

11
répondu R.. 2011-02-14 09:06:44

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

j'ai collé du code ci - dessous (mes premiers pas avec un mutex-j'ai appris la concurrence sur win32 avec HANDLE, SetEvent, WaitForMultipleObjects etc).

puisque c'est ma première tentative avec std::mutex et amis, j'aimerais voir des commentaires, des suggestions et des améliorations!

#include <condition_variable>
#include <mutex>
#include <algorithm>
#include <thread>
#include <queue>
#include <chrono>
#include <iostream>


int _tmain(int argc, _TCHAR* argv[])
{   
    // these vars are shared among the following threads
    std::queue<unsigned int>    nNumbers;

    std::mutex                  mtxQueue;
    std::condition_variable     cvQueue;
    bool                        m_bQueueLocked = false;

    std::mutex                  mtxQuit;
    std::condition_variable     cvQuit;
    bool                        m_bQuit = false;


    std::thread thrQuit(
        [&]()
        {
            using namespace std;            

            this_thread::sleep_for(chrono::seconds(5));

            // set event by setting the bool variable to true
            // then notifying via the condition variable
            m_bQuit = true;
            cvQuit.notify_all();
        }
    );


    std::thread thrProducer(
        [&]()
        {
            using namespace std;

            int nNum = 13;
            unique_lock<mutex> lock( mtxQuit );

            while ( ! m_bQuit )
            {
                while( cvQuit.wait_for( lock, chrono::milliseconds(75) ) == cv_status::timeout )
                {
                    nNum = nNum + 13 / 2;

                    unique_lock<mutex> qLock(mtxQueue);
                    cout << "Produced: " << nNum << "\n";
                    nNumbers.push( nNum );
                }
            }
        }   
    );

    std::thread thrConsumer(
        [&]()
        {
            using namespace std;
            unique_lock<mutex> lock(mtxQuit);

            while( cvQuit.wait_for(lock, chrono::milliseconds(150)) == cv_status::timeout )
            {
                unique_lock<mutex> qLock(mtxQueue);
                if( nNumbers.size() > 0 )
                {
                    cout << "Consumed: " << nNumbers.front() << "\n";
                    nNumbers.pop();
                }               
            }
        }
    );

    thrQuit.join();
    thrProducer.join();
    thrConsumer.join();

    return 0;
}
7
répondu fishfood 2012-11-12 23:20:11

la fonction pthread_mutex_lock() soit acquiert le mutex pour le fil appelant ou bloque le fil jusqu'à ce que le mutex puisse être acquis. Le pthread_mutex_unlock() produit le mutex.

Penser le mutex comme une file d'attente; chaque thread qui tente d'acquérir le mutex sera placé sur la fin de la file d'attente. Quand un thread libère le mutex, le thread suivant dans la file d'attente s'éteint et est maintenant en cours d'exécution.

Un la section critique fait référence à une région de code où le non-déterminisme est possible. Souvent, cela parce que plusieurs threads tentent d'accéder à une variable partagée. La section critique n'est pas sûre tant qu'une sorte de synchronisation n'est pas en place. Une serrure mutex est une forme de synchronisation.

4
répondu chrisaycock 2011-02-14 06:44:00

vous êtes censé vérifier la variable mutex avant d'utiliser la zone protégée par le mutex. Ainsi, votre pthread_mutex_lock () pourrait (selon l'implémentation) attendre que mutex1 soit libéré ou retourner une valeur indiquant que la serrure ne pourrait pas être obtenue si quelqu'un d'autre l'a déjà verrouillée.

Mutex est vraiment juste un sémaphore simplifié. Si vous lisez sur eux et les comprenez, vous comprenez les Mutex. Il y a plusieurs questions concernant les mutex et les sémaphores dans la. différence entre le sémaphore binaire et le mutex , quand devrions-nous utiliser mutex et quand devrions-nous utiliser semaphore et ainsi de suite. Les toilettes exemple dans le premier lien est sur un exemple que l'on peut penser. Tous les code n'est de vérifier si la clé n'est disponible et si elle l'est, la réserve. Remarquez que vous ne réservez pas vraiment la toilette elle-même, mais la clé.

3
répondu Makis 2017-05-23 12:03:09

EXEMPLE DE SÉMAPHORE::

sem_t m;
sem_init(&m, 0, 0); // initialize semaphore to 0

sem_wait(&m);
// critical section here
sem_post(&m);

référence: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt

2
répondu parasrish 2015-08-03 17:56:00

pour ceux qui cherchent l'exemple du vortex mutex:

#include <mutex>
using namespace std;

int main() {
    mutex m;

    m.lock();
    // do thread-safe stuff
    m.unlock();

    return 0;
}
0
répondu nathangeorge1 2018-07-24 15:26:03