lundi 25 octobre 2010

Sync C# - Mutex

Description du Mutex
Mutex signifie Mutuellement Exclusif.
C'est par exemple le cas d'un WC. Si quelqu'un est occupé dans la toilette, cette dernière n'est pas disponible pour une autre personne (sont usage est mutuellement exclusif). Le verrou est fermé lorsque la personne entre dans le WC et ouvert lorsqu'elle a terminée. Le verrou est le "mutex" protégeant une ressource non partageable (le wc) pendant son utilisation.
Les autres personnes désirant utiliser le WC font alors la file et attendent patiemment que l'occupant libère les lieux (le verrou).

Plus informatique-ment parlant...
A un moment quelconque du fonctionnement du logiciel (ou des logiciels puisqu'un mutex est cross-process), le mutex ne peut être détenu que par un et un seul processus à la fois. Les autres processus désirant acquérir le mutex doivent alors se montrer patient et attendre qu'il soit libéré.
Un mutex est donc bien pratique pour indiquer qu'une ressource non partageable est en cours d'utilisation.
Il permet de créer des sections critiques comme le fait l'instruction lock mais avec quelques différences notables; à savoir qu'un mutex est beaucoup plus lent et couteux en ressource qu'un lock.
Cette lenteur du mutex est due au fait que les mutexes sont gérés par le système d'exploitation alors que le lock est propre à la plateforme .Net.
Ce désavantage de la lenteur inhérent au système d'exploitation est aussi une grande force puisque deux processus distinct fonctionnant sur une même machine (et même écrits dans des langages différents) peuvent synchroniser leurs accès à une ressource commune à l'aide de Mutex.

Utilisation des Mutexes en .Net
Au sein d'un même logiciel .Net, partager la référence vers l'instance de l'objet Mutex est suffisant pour faire la synchronisation.
Par contre, si le Mutex doit être utilisé par d'autres logiciels ou d'autres instances d'un même logiciel, il est alors nécessaire de le nommé. C'est le nom du mutex qui sera utilisé par le Kernel Windows pour faire les synchronisation.

Exemples
Exemple de Named Mutex
Cet exemple démontre l'usage des named mutex.
Le but ici est de détecter de façon assez rudimentaire si l'application fonctionne déjà. Essayez donc de démarrer en même temps deux instances de ce snippet
Note: il serait également possible de le savoir en consultant le compteur d'instance.

Fichier: Threading_NamedMutex.cs


using System;
using System.Collections.Generic;
using System.Threading;

public class MyClass
{
    static Mutex mutex = new Mutex( false, "Blog.Domeu.Net-OneAppAtTheTime" );
    
    public static void RunSnippet()
    {
        WL( "Starting" );
        // Detect if sofware is already running
        //   Tested during some seconds (in case when other software is shuting down).
        if( !mutex.WaitOne(TimeSpan.FromSeconds(5) , false )){
            WL("Oups, another software is already running");
            return;
        }
        WL( "Started" );
        WL( "Press enter to exit");
        RL();
        // Always release the mutex
        //    Otherwise other processed attempting to acquire it will received an exception 
        //    when the mutex would be abandoned by this process.
        mutex.ReleaseMutex();
        
            
        
    }
    
    #region Helper methods 
    ...
    #endregion
}

Exemple de partage de référence
Cet exemple démontre l'usage d'un mutex non nommé.
Ici, le partage de la référence du mutex permet a un seul des deux threads (a la fois) de modifier une variable globale.
Note: cet exemple est a pure usage didactique, l'utilisation d'un lock aurait été beaucoup plus efficient.

Fichier: Threading_UnNamedMutex.cs

/*  This sample demonstrate the usage of UN-NAMED MUTEX having a reference shared among the threads.

    The sample is simple, each thread can increase the value of a shared variable (SharedValue).
    A mutex is used to avoids race condition.
    
    NB: Usage of Lock instruction would give the same result with better performance but mutex was use 
        for the purpose of the sample.
*/
using System;
using System.Collections.Generic;
using System.Threading;

public class MyClass
{
    private static Mutex sharedMutex = new Mutex();
    private static int SharedValue = 0; 
    
    public static void RunSnippet()
    {
        List<Thread> threadList = new List<Thread>();
        // Create Threads
        for( int i = 1; i<=2; i++ ){
            Thread aThread = new Thread( new ThreadStart( DoWork ) );
            threadList.Add( aThread );
        }
        
        // Start the threads
        foreach( Thread th in threadList )
            th.Start();
        
        // Wait that all threads get finished
        threadList.ForEach( th => th.Join() );
        
    }
    
    public static void DoWork() {
        int value = 0;
        while( value < 1000 ) {
            // Update the SharedValue + take a copy
            sharedMutex.WaitOne(); // Use the mutex
            SharedValue++;
            value=SharedValue;
            sharedMutex.ReleaseMutex(); 
            // Display value every 10 occurences
            if( (value%10) == 0 )
              WL( String.Format( "Thread {0} updated SharedValue to {1}", Thread.CurrentThread.GetHashCode(), value ));
        }
        WL( String.Format("Thread {0} get completed", Thread.CurrentThread.GetHashCode() ) );
    }
    
    #region Helper methods 
    ...
    #endregion
}

Aucun commentaire: