Ca y est, la mode est lancée, on n'entend plus parler que de programmation asynchrone, parrallèlle, etc. Tout ça en prévision d'un futur, pas si lointain que ça, où nous n'aurons plus assé de doigts pour compter le nombre de core processeur notre pc contient.
Le multithreadé est l'avenir, et bien que l'on espère voir apparaître un jour un super framework qui gérera tout ça pour nous en attendant il faut bien ressortir nos vieux bouquins et se demander : "mais, et si je devais programmer du multithreadé maintenant, et si je voulais le faire bien pour une foi, quel patern devrais-je appliquer?"
Et bien, rien de plus simple ;) (Ca fait peur quand ça commence comme ça non?)
Tout d'abord, revoyons rapidement deux bases : les mutex et les events.
Mutex
Le mutex est un des moyens les plus bas niveau pour proteger l'acces concurent à une ressource partagée.
On peut voir un mutex comme une clefs unique pour un verou unique. Si je prend la clefs, mon voisin ne saura pas la prendre tant que je ne l'aurais pas remise sur la table, et donc va attendre que je la repose avant de continuer.
En C# la class Mutex contient 2 methodes indispensable :
- WaitOne() : (ou "Prendre la clefs") Vérifie qu'aucun autre thread n'a la main sur le Mutex, dans le cas contraire, bloque le thread appellant jusqu'à la libération du Mutex par celui qui le possède.
- ReleaseMutex() : (ou "Rendre la clefs") Libère le Mutex.
Voici un exemple de code pour illustrer les Mutex, dans ce cas-ci aucune ressource n'est protégée, mais le principe est le même, la ressource protégée doit être accédée uniquement quand le thread courant possède le Mutex. (Dans le cas d'un Mutex, si l'on ne joue pas le jeu, le système ne criera pas sur vous).
[code:c#]
using System.Threading;
namespace Blog
{
class Program
{
private void ThreadWriteLine(string s)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " > " + s);
}
public void TimerCallback(object MutexHandle)
{
ThreadWriteLine("Waiting mutex");
(MutexHandle as Mutex).WaitOne();
ThreadWriteLine("I have the mutex ! --> sleep");
Thread.Sleep(5000);
ThreadWriteLine("Releasing mutex");
(MutexHandle as Mutex).ReleaseMutex();
}
static void Main(string[] args)
{
Program p = new Program();
Mutex MutexHandle = new Mutex(false);
for (int i = 0; i < 5; i++)
{
Timer T = new Timer(new TimerCallback(p.TimerCallback), MutexHandle, 0, 0);
}
Console.ReadKey();
}
}
}
[/code]
AutoResetEvent
Les évents, ou plustôt les AutoResetEvents vont nous permettre d'envoyer un signal d'un thread à un autre.
Cette Class contient elle aussi deux méthodes :
- WaitOne() : met le thread courent en attente jusqu'à ce qu'un autre emette un évènement sur ce même AutoResetEvent
- Set() : emet un évènement sur un AutoResetEvent

Une foi de plus, voici un petit bout de code pour illustrer l'utilisation des AutoResetEvent :
[code:c#]
namespace Blog
{
class Program
{
public void TimerCallback(Object waitHandle)
{
ThreadWriteLine(
"TimeCallBack Begin");
Thread.Sleep(1000);
(waitHandle
as AutoResetEvent).Set();
}
static void Main(string[] args)
{
Program p = new Program();
AutoResetEvent waitHandle = new AutoResetEvent(false);
Timer T = new Timer(new TimerCallback(p.TimerCallback), waitHandle, 0, 0);
waitHandle.WaitOne();
p.ThreadWriteLine("Event Fired");
Console.WriteLine("Press a key");
Console.ReadKey();
}
private void ThreadWriteLine(string s)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " > " + s);
}
}
}
[/code]
ReaderWriterLock
Nous avons vu qu'un Mutex pouvait protéger une ressource,mais avouons que c'est une méthode très bas niveau. Le framework .Net nous offre des class toute faites, prévue pour chaque situation, tel que celle-ci, le ReaderWriterLock.
Le ReaderWriterLock est une class qui permet de limiter l'acces à une ressource, de manière unitaire en écriture mais de manière concurente en lecture.
Soit :
- Si deux threads essayent d'écrire dans la ressource simultanément, l'un des deux va devoir attendre la fin du travail du suivant
- Si deux threads essayent de lire la ressource et qu'aucun autre thread n'est en train d'y écrire, il vont pouvoir le faire simultanément
Nous alos donc, cette foi, avoir 4 méthodes au lieu de 2 :
- AcquireWriterLock() : demande de permission d'écrirure, si un autre thread à déjà cette permission, cette méthode est bloquante
- ReleaseWriterLock() : libère la permission d'écriture
- AcquireReaderLock() : demande de permission de lecture, su un autre thread possède la permission d'écrire, cette méthode est bloquante
- ReleaseReaderLock() : libère la permission de lecture
Dans l'exemple qui suit, un thread writer crée un thread enfant demandant une permission de lecture, on constate que cette permission ne lui est accordée que quand le thread writer à effectué son ReleaseWriterLock().
[code:c#]
using System;
using System.Threading;
namespace ConsoleApplication9
{
class Program
{
private ReaderWriterLock LockHandle;
private string val;
public Program()
{
LockHandle = new ReaderWriterLock();
val = "";
}
private void ThreadWriteLine(string s)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + " > " + s);
}
public void Read()
{
LockHandle.AcquireReaderLock(Timeout.Infinite);
ThreadWriteLine("AcquireReaderLock > val :'" + val + "'");
Thread.Sleep(1000);
ThreadWriteLine("ReleaseReaderLock > val :'" + val + "'");
LockHandle.ReleaseReaderLock();
}
public void Write()
{
LockHandle.AcquireWriterLock(Timeout.Infinite);
ThreadWriteLine("AcquireWriterLock! > writing start");
int id = LockHandle.WriterSeqNum;
ThreadWriteLine("WriterSeqNum = " + id);
Thread.Sleep(1000);
val = "Hello ";
Thread.Sleep(1000);
(new Thread(new ThreadStart(this.Read))).Start();
val += "World ";
Thread.Sleep(1000);
val += "I'm ";
Thread.Sleep(1000);
val += "a writer !";
Thread.Sleep(1000);
ThreadWriteLine("ReleaseWriterLock");
LockHandle.ReleaseWriterLock();
}
static void Main(string[] args)
{
Program p = new Program();
Thread[] writer = new Thread[10];
for (int i = 0; i < 3; i++)
{
writer[i] = new Thread(new ThreadStart(p.Write));
}
for (int i = 0; i < 3; i++)
{
writer[i].Start();
}
Console.ReadKey();
}
}
}
[/code]
NB : Ceci n'est qu'un exemple, généralement, éviter d'utiliser ThreadStart, choisisez de préférence l'utilisation du pool de thread de l'application.
Conclusion
Il existe déjà dans le monde .Net de nombreux moyens pour synchroniser différents threads lorsque néccessaire. De nombreuses règles de bonnes pratique existe et nous évite de nous arracher les cheveux.
Ce que l'avenir nous réserve dans tout ça ? Avec la mutiplication des traîtements parrallèle, l'écriture "humaine" d'une gestion de ces traîtements sera rapidement longue, fastidieuse, voir impossible. Nous allons voir apparaître de nouveaux languages (tel que le F#) ainsi que des librairies pour les languages existants (tel que Microsoft.Research.Join) nous permettant une programmation plus déclarative que celle à laquelle nous sommes habitué.
Mais je pense qu'il ne faut pas négliger les bases, ces concepts, même s'ils deviendront peut-être invisibles, seront de plus en plus existant au sein même de nos applications.