Synchronisation des Threads
Posted: Sun Jan 18, 2026 6:53 pm
## Synchronisation des threads en Win32
Maintenant que tu sais créer des threads, on arrive au **vrai sujet sérieux** :
la **synchronisation**.
Créer des threads, c’est facile.
Les synchroniser correctement, c’est là que commencent :
* les bugs bizarres
* les crashs aléatoires
* les comportements impossibles à reproduire
Donc ce cours est **très important**.
---
### Pourquoi la synchronisation est nécessaire
Tous les threads d’un même processus partagent :
* la mémoire
* les variables globales
* le heap
* les ressources
Si deux threads accèdent **en même temps** à la même ressource :
* le résultat devient imprévisible
* le programme peut planter
* ou pire : fonctionner “presque” correctement
C’est ce qu’on appelle une **race condition**.
La synchronisation ne sert pas à aller plus vite.
Elle sert à **éviter que tout parte en vrille**.
---
## Les objets de synchronisation Win32
Windows fournit plusieurs mécanismes.
Ils n’ont **pas tous le même rôle**.
---
### Critical Section
La **Critical Section** est l’outil le plus utilisé **dans un seul processus**.
Caractéristiques :
* très rapide
* fonctionne uniquement intra-process
* pas visible entre processus
Principe :
* un seul thread peut entrer dans la section protégée
* les autres attendent
Exemple :
À utiliser quand :
* tu travailles dans un seul processus
* tu veux de la performance
* tu protèges des variables partagées
---
### Mutex
Le **Mutex** ressemble à une Critical Section, mais en plus lourd.
Différences :
* peut être partagé entre processus
* plus lent
* basé sur un objet noyau
Exemple :
À utiliser quand :
* plusieurs processus doivent accéder à une ressource
* la performance n’est pas critique
---
### SRW Lock (Slim Reader/Writer Lock)
Le **SRW Lock** est plus moderne.
Il permet :
* plusieurs lecteurs en même temps
* un seul écrivain
Très utile quand :
* les lectures sont fréquentes
* les écritures rares
Exemple :
À utiliser quand :
* tu as beaucoup de lectures
* tu veux de bonnes performances
---
### Semaphore
Le **Semaphore** permet de limiter l’accès à une ressource à **N threads**.
Exemple :
* pool de connexions
* nombre limité de ressources
Exemple :
Ici :
* seulement 2 threads peuvent entrer en même temps
---
### Event
Un **Event** sert à **signaler quelque chose** entre threads.
Il ne protège pas une ressource.
Il sert à dire :
“Tu peux continuer”.
Deux types :
* auto-reset
* manual-reset
Exemple :
À utiliser pour :
* synchroniser des étapes
* attendre un signal
* coordination entre threads
---
## Attendre : WaitForSingleObject
La fonction **WaitForSingleObject** permet d’attendre :
* un mutex
* un event
* un thread
* un semaphore
Exemple :
Ici :
* le thread appelant est bloqué
* jusqu’à la fin du thread attendu
---
## Attendre plusieurs objets : WaitForMultipleObjects
Quand tu dois attendre **plusieurs événements**, tu utilises :
Exemple :
Ici :
* le thread se débloque quand **un des deux** events est signalé
Très utile pour :
* gérer plusieurs threads
* arrêter proprement une boucle
* attendre plusieurs signaux
---
## Erreurs classiques
Créer trop de threads
Utiliser des mutex partout
Oublier de libérer un verrou
Faire des sections critiques trop longues
Mélanger les mécanismes sans comprendre
Résultat :
* deadlocks
* performances catastrophiques
* bugs aléatoires
---
## Deadlock (à comprendre absolument)
Un deadlock arrive quand :
* thread A attend une ressource détenue par B
* thread B attend une ressource détenue par A
Plus rien n’avance.
Bonne pratique :
* toujours prendre les verrous dans le même ordre
* garder les sections critiques courtes
* réfléchir avant de verrouiller
---
## À retenir
La synchronisation est obligatoire dès que plusieurs threads partagent des données.
Critical Section :
* rapide
* intra-process
Mutex :
* inter-process
* plus lent
SRW Lock :
* lecteur / écrivain
* performant
Semaphore :
* limite le nombre d’accès
Event :
* signalisation
WaitForSingleObject :
* attendre un objet
WaitForMultipleObjects :
* attendre plusieurs objets
---
La synchronisation est souvent la partie la plus difficile du multithreading.
Mais quand tu la maîtrises, ton code devient :
* stable
* prévisible
* robuste
Dans les prochains cours, on verra :
* des exemples de race condition réelles
* comment provoquer un deadlock
* comment les détecter
* et comment écrire du code multi-thread propre.
Maintenant que tu sais créer des threads, on arrive au **vrai sujet sérieux** :
la **synchronisation**.
Créer des threads, c’est facile.
Les synchroniser correctement, c’est là que commencent :
* les bugs bizarres
* les crashs aléatoires
* les comportements impossibles à reproduire
Donc ce cours est **très important**.
---
### Pourquoi la synchronisation est nécessaire
Tous les threads d’un même processus partagent :
* la mémoire
* les variables globales
* le heap
* les ressources
Si deux threads accèdent **en même temps** à la même ressource :
* le résultat devient imprévisible
* le programme peut planter
* ou pire : fonctionner “presque” correctement
C’est ce qu’on appelle une **race condition**.
La synchronisation ne sert pas à aller plus vite.
Elle sert à **éviter que tout parte en vrille**.
---
## Les objets de synchronisation Win32
Windows fournit plusieurs mécanismes.
Ils n’ont **pas tous le même rôle**.
---
### Critical Section
La **Critical Section** est l’outil le plus utilisé **dans un seul processus**.
Caractéristiques :
* très rapide
* fonctionne uniquement intra-process
* pas visible entre processus
Principe :
* un seul thread peut entrer dans la section protégée
* les autres attendent
Exemple :
Code: Select all
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
// section critique
sharedValue++;
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
* tu travailles dans un seul processus
* tu veux de la performance
* tu protèges des variables partagées
---
### Mutex
Le **Mutex** ressemble à une Critical Section, mais en plus lourd.
Différences :
* peut être partagé entre processus
* plus lent
* basé sur un objet noyau
Exemple :
Code: Select all
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
WaitForSingleObject(hMutex, INFINITE);
// section protégée
ReleaseMutex(hMutex);
CloseHandle(hMutex);
* plusieurs processus doivent accéder à une ressource
* la performance n’est pas critique
---
### SRW Lock (Slim Reader/Writer Lock)
Le **SRW Lock** est plus moderne.
Il permet :
* plusieurs lecteurs en même temps
* un seul écrivain
Très utile quand :
* les lectures sont fréquentes
* les écritures rares
Exemple :
Code: Select all
SRWLOCK lock = SRWLOCK_INIT;
// lecture
AcquireSRWLockShared(&lock);
// lire les données
ReleaseSRWLockShared(&lock);
// écriture
AcquireSRWLockExclusive(&lock);
// modifier les données
ReleaseSRWLockExclusive(&lock);
* tu as beaucoup de lectures
* tu veux de bonnes performances
---
### Semaphore
Le **Semaphore** permet de limiter l’accès à une ressource à **N threads**.
Exemple :
* pool de connexions
* nombre limité de ressources
Exemple :
Code: Select all
HANDLE hSem = CreateSemaphore(NULL, 2, 2, NULL);
WaitForSingleObject(hSem, INFINITE);
// accès à la ressource
ReleaseSemaphore(hSem, 1, NULL);
CloseHandle(hSem);
* seulement 2 threads peuvent entrer en même temps
---
### Event
Un **Event** sert à **signaler quelque chose** entre threads.
Il ne protège pas une ressource.
Il sert à dire :
“Tu peux continuer”.
Deux types :
* auto-reset
* manual-reset
Exemple :
Code: Select all
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
// thread A
SetEvent(hEvent);
// thread B
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hEvent);
* synchroniser des étapes
* attendre un signal
* coordination entre threads
---
## Attendre : WaitForSingleObject
La fonction **WaitForSingleObject** permet d’attendre :
* un mutex
* un event
* un thread
* un semaphore
Exemple :
Code: Select all
WaitForSingleObject(hThread, INFINITE);
* le thread appelant est bloqué
* jusqu’à la fin du thread attendu
---
## Attendre plusieurs objets : WaitForMultipleObjects
Quand tu dois attendre **plusieurs événements**, tu utilises :
Code: Select all
WaitForMultipleObjects(
count,
handles,
FALSE,
INFINITE
);
Code: Select all
HANDLE handles[2] = { hEvent1, hEvent2 };
WaitForMultipleObjects(2, handles, FALSE, INFINITE);
* le thread se débloque quand **un des deux** events est signalé
Très utile pour :
* gérer plusieurs threads
* arrêter proprement une boucle
* attendre plusieurs signaux
---
## Erreurs classiques
Créer trop de threads
Utiliser des mutex partout
Oublier de libérer un verrou
Faire des sections critiques trop longues
Mélanger les mécanismes sans comprendre
Résultat :
* deadlocks
* performances catastrophiques
* bugs aléatoires
---
## Deadlock (à comprendre absolument)
Un deadlock arrive quand :
* thread A attend une ressource détenue par B
* thread B attend une ressource détenue par A
Plus rien n’avance.
Bonne pratique :
* toujours prendre les verrous dans le même ordre
* garder les sections critiques courtes
* réfléchir avant de verrouiller
---
## À retenir
La synchronisation est obligatoire dès que plusieurs threads partagent des données.
Critical Section :
* rapide
* intra-process
Mutex :
* inter-process
* plus lent
SRW Lock :
* lecteur / écrivain
* performant
Semaphore :
* limite le nombre d’accès
Event :
* signalisation
WaitForSingleObject :
* attendre un objet
WaitForMultipleObjects :
* attendre plusieurs objets
---
La synchronisation est souvent la partie la plus difficile du multithreading.
Mais quand tu la maîtrises, ton code devient :
* stable
* prévisible
* robuste
Dans les prochains cours, on verra :
* des exemples de race condition réelles
* comment provoquer un deadlock
* comment les détecter
* et comment écrire du code multi-thread propre.