mercredi 8 octobre 2025

Q/A Multithreading et Concurrence



🔹 1. Pourquoi ConcurrentDictionary<TKey, TValue> est préféré à Dictionary en multi-thread ?

  • Thread-safe pour lecture et écriture concurrentes.

  • Evite les exceptions et incohérences de données (InvalidOperationException ou corruption de mémoire).

  • Optimisé pour opérations atomiques comme TryAdd, TryUpdate, GetOrAdd.

  • Exemple :

var dict = new ConcurrentDictionary<string, double>();
dict.TryAdd("AAPL", 150.5);
dict.AddOrUpdate("AAPL", 150.5, (key, oldValue) => oldValue + 1);

✅ Dans un environnement multi-threadé, Dictionary nécessite un lock externe pour garantir la sécurité.


🔹 2. CancellationToken et utilisation dans les tâches longues

  • Permet d’annuler proprement des tâches en cours sans les forcer.

  • Méthode : passer un CancellationToken à la tâche et vérifier régulièrement token.IsCancellationRequested.

  • Exemple :

public async Task ComputeRiskAsync(CancellationToken token)
{
    for (int i = 0; i < 1_000_000; i++)
    {
        token.ThrowIfCancellationRequested();
        // calcul lourd
    }
}
  • Avantage : réactivité + nettoyage des ressources.


🔹 3. Différence entre lock, Monitor, SemaphoreSlim, Mutex

Outil Scope Usage Performance / Latence
lock(obj) Thread local Simplifie Monitor.Enter/Exit Rapide, syntaxe simple
Monitor Thread local Contrôle plus fin (TryEnter, Wait/Pulse) Flexibilité, légèrement plus verbeux
SemaphoreSlim Multi-thread Limite nombre de threads concurrents Asynchrone-friendly (WaitAsync)
Mutex Cross-process Synchronisation entre processus Plus lent, kernel-level

🔹 4. Tester et éviter les conditions de course (race conditions)

✅ Stratégies :

  • Utiliser des collections thread-safe (ConcurrentDictionary, ConcurrentQueue)

  • Utiliser des locks ou Interlocked pour opérations atomiques

  • Unit tests multi-threadés et stress tests

  • Static code analysis et tools comme Microsoft CHESS


🔹 5. Lazy pour initialisation d’objets lourds

  • Crée l’objet uniquement lorsqu’il est utilisé pour la première fois

  • Thread-safe par défaut (LazyThreadSafetyMode.ExecutionAndPublication)

  • Exemple :

Lazy<RiskEngine> engine = new Lazy<RiskEngine>(() => new RiskEngine());
var result = engine.Value; // initialisation ici

✅ Utile pour les objets de pricing ou risk engine qui consomment beaucoup de mémoire.


🔹 6. Stratégies pour éviter les deadlocks

  1. Ordre fixe pour acquérir les locks

  2. Utiliser timeout sur Monitor.TryEnter / SemaphoreSlim.Wait

  3. Éviter locks imbriqués lorsque possible

  4. Privilégier lock-free structures et ConcurrentDictionary

  5. Limiter la durée de chaque lock → lock court et ciblé


🔹 7. Utilisation de Task.WhenAll() et Task.WhenAny()

  • Task.WhenAll() : attendre plusieurs tâches parallèles et récupérer les résultats

var tasks = symbols.Select(s => ComputePriceAsync(s));
var results = await Task.WhenAll(tasks);
  • Task.WhenAny() : continuer dès qu’une tâche se termine (utile pour first response wins)

var first = await Task.WhenAny(tasks);

🔹 8. Différence entre async/await et TPL (Task Parallel Library)

Concept Description
async/await Syntaxe simplifiée pour programmation asynchrone (IO bound)
TPL (Task Parallel Library) Gestion avancée de parallelisme CPU-bound, Task.Run, Parallel.ForEach

✅ Exemple : async/await = streaming de données marché, TPL = calculs massifs sur portefeuilles.


🔹 9. Profiler et optimiser les performances multi-threading

  • Outils :

    • Visual Studio Profiler, JetBrains dotTrace, PerfView

    • Thread contention analysis, GC pauses

  • Optimisations :

    • Minimiser locks → préférer collections thread-safe

    • Pré-allocation mémoire → éviter GC dans hot path

    • Batch processing → réduire fréquence context-switch

    • Affinity threads → CPU pinning pour threads critiques


🔹 10. Utilisation de SpinLock en latence ultra-faible

  • SpinLock : boucle active au lieu de bloquer le thread

  • Idéal pour section critique très courte

  • Pas adapté aux sections longues → CPU waste

  • Exemple :

SpinLock spin = new SpinLock();
bool lockTaken = false;
try
{
    spin.Enter(ref lockTaken);
    // code ultra court et critique
}
finally
{
    if (lockTaken) spin.Exit();
}

✅ Utilisé en HFT / trading ultra-low latency pour éviter context switch du kernel.


📘 Résumé des bonnes pratiques Multi-threading / Concurrence

Concept Astuce / Usage
ConcurrentDictionary Thread-safe, opérations atomiques
CancellationToken Annulation propre, réactivité
lock / Monitor / SemaphoreSlim / Mutex Choisir selon scope, async, cross-process
Race conditions Tests multi-thread, collections thread-safe
Lazy Initialisation différée thread-safe
Deadlocks Locks courts, ordre fixe, timeout
Task.WhenAll / Task.WhenAny Synchroniser tâches parallèles
async/await vs TPL IO-bound vs CPU-bound
SpinLock Sections critiques ultra-courtes, faible latence
Profiling GC, contention, hot path, allocation pools


Aucun commentaire:

Enregistrer un commentaire