4.6 KiB
Règles de concurrence
Vue d'ensemble
Lardon3D utilise un modèle de concurrence à thread unique pour ncurses et un modèle multi-thread pour le traitement. La séparation est stricte : le thread ncurses ne fait jamais de travail métier, et les workers ne touchent jamais ncurses.
Modèle de concurrence
Thread principal (ncurses)
├── Gestion des entrées
├── Affichage TUI
└── Orchestration
Worker thread
├── Exécution des tâches
├── Calculs métier
└── Écritures de résultats
Règles fondamentales
1. ncurses appartient au thread principal
// ✅ Correct : appel depuis le thread principal
mvprintw(0, 0, "Progression: %d%%", progress);
// ❌ Interdit : appel depuis un worker
// mvprintw() dans un thread secondaire
2. Variables partagées protégées par mutex
// ✅ Correct
pthread_mutex_lock(&queue->mutex);
queue->count++;
pthread_mutex_unlock(&queue->mutex);
// ❌ Interdit
// queue->count++; sans protection
3. Variables de condition pour la synchronisation
// Producteur (scheduler)
pthread_mutex_lock(&queue->mutex);
queue->ready = true;
pthread_cond_signal(&queue->cond);
pthread_mutex_unlock(&queue->mutex);
// Consommateur (worker)
pthread_mutex_lock(&queue->mutex);
while (!queue->ready) {
pthread_cond_wait(&queue->cond, &queue->mutex);
}
// traitement
pthread_mutex_unlock(&queue->mutex);
4. Pas de callback ncurses depuis un worker
// ✅ Correct : le worker signale au thread principal
void worker_callback(task_t *task, void *userdata) {
shared_state_t *state = userdata;
pthread_mutex_lock(&state->mutex);
state->result_ready = true;
pthread_cond_signal(&state->cond);
pthread_mutex_unlock(&state->mutex);
}
// ❌ Interdit : appel ncurses depuis le worker
// void worker_callback(...) {
// mvprintw(...);
// }
Primitives utilisées
| Primitive | Usage |
|---|---|
pthread_mutex_t |
Protection des données partagées |
pthread_cond_t |
Synchronisation producteur/consommateur |
pthread_create() |
Création des workers |
pthread_join() |
Attente de fin des workers |
pthread_cancel() |
Annulation d'un worker (dernier recours) |
Invariants de concurrence
-
Un seul thread ncurses : ncurses n'est jamais appelé depuis un worker. Toute mise à jour de l'UI passe par des variables partagées protégées.
-
Mutex hiérarchique : si plusieurs mutex sont acquis, toujours dans le même ordre pour éviter les deadlocks.
-
Annulation coopérative : les workers vérifient périodiquement un drapeau d'annulation. Pas d'interruption brutale sauf dernier recours.
-
Réservation atomique : la réservation du gouverneur est atomique. Deux threads ne peuvent pas obtenir la même réservation.
-
Pas de callback sans réservation : aucun callback de tâche n'est invoqué sans réservation active. Cet invariant est maintenu même en présence d'erreurs.
Anti-patterns
Deadlock
// ❌ Risque de deadlock
pthread_mutex_lock(&mutex_a);
pthread_mutex_lock(&mutex_b); // attend mutex_b
// Dans un autre thread :
pthread_mutex_lock(&mutex_b);
pthread_mutex_lock(&mutex_a); // attend mutex_a → DEADLOCK
Solution : toujours acquérir les mutex dans le même ordre.
Race condition
// ❌ Race condition
if (task->state == TASK_STATE_QUEUED) {
task->state = TASK_STATE_RUNNING;
}
// ✅ Correct
pthread_mutex_lock(&task->mutex);
if (task->state == TASK_STATE_QUEUED) {
task->state = TASK_STATE_RUNNING;
}
pthread_mutex_unlock(&task->mutex);
Use-after-free
// ❌ Use-after-free
task_destroy(task);
task_callback(task); // task est libéré
// ✅ Correct
task_callback(task);
task_destroy(task);
Validation
Pour tout ticket touchant la concurrence, exécuter :
# Build TSan
meson setup build-tsan --wipe -Db_sanitize=thread -Db_static=false
meson compile -C build-tsan -j8
meson test -C build-tsan --print-errorlogs
TSan détecte automatiquement :
- les race conditions
- les deadlocks potentiels
- les signaux perdus
- les verrous non libérés
Checklist de concurrence
Avant de livrer un ticket touchant la concurrence :
- Toutes les variables partagées sont protégées par un mutex
- Les mutex sont toujours libérés (même en cas d'erreur)
- Les variables de condition sont vérifiées dans une boucle
while - Aucun appel ncurses depuis un worker
- L'annulation est coopérative (pas de
pthread_cancelsauf dernier recours) - TSan ne signale aucune erreur
- Le build ASan ne signale aucune fuite mémoire liée aux threads