Dans un environnement multithreading vous devez prendre soin de synchronisation de sorte que deux threads n'a pas les fringues de l'état, en en effectuant simultanément des modifications. Sinon vous pouvez avoir des conditions de course dans votre code (pour un exemple, voir l' infâme Therac-25 accident.) Vous devez également planifier les threads pour effectuer diverses tâches. Ensuite, vous devez vous assurer que vos paramètres de synchronisation, et de la programmation n'est pas de provoquer un blocage où plusieurs threads en attente pour l'autre indéfiniment.
La synchronisation
Quelque chose d'aussi simple que d'augmenter un compteur exige de synchronisation:
counter += 1;
Assumer cette séquence d'événements:
-
counter
est initialisé à 0
- thread récupère
counter
de la mémoire vers le processeur (0)
- changement de contexte
- thread B récupère
counter
de la mémoire vers le processeur (0)
- thread B augmentations
counter
sur le cpu
- le fil B réécrit
counter
de cpu à la mémoire (1)
- changement de contexte
- enfiler les augmentations
counter
sur le cpu
- fil A écrit
counter
de cpu à la mémoire (1)
À ce stade, l' counter
est de 1, mais les deux fils n'a essayer de l'augmenter. L'accès à ce que le compteur doit être synchronisée par une sorte de mécanisme de verrouillage:
lock (myLock) {
counter += 1;
}
Un seul thread est autorisé à exécuter le code à l'intérieur du bloc verrouillé. Deux threads s'exécutant ce code peut entraîner dans cette séquence d'événements:
- compteur est initialisé à 0
- enfiler Une acquiert
myLock
- changement de contexte
- le fil B essaie d'acquérir
myLock
mais a plus qu'à attendre
- changement de contexte
- thread récupère
counter
de la mémoire vers le processeur (0)
- enfiler les augmentations
counter
sur le cpu
- fil A écrit
counter
de cpu à la mémoire (1)
- enfiler les versions
myLock
- changement de contexte
- le fil B acquiert
myLock
- thread B récupère
counter
de la mémoire vers le processeur (1)
- thread B augmentations
counter
sur le cpu
- le fil B réécrit
counter
de cpu à la mémoire (2)
- le fil B communiqués
myLock
À ce point - counter
2.
La planification
La planification est une autre forme de synchronisation et vous devez vous utilisez la synchronisation des threads mécanismes, tels que les événements, les sémaphores, de la transmission de message etc. pour démarrer et arrêter les threads. Voici un exemple simplifié en C#:
AutoResetEvent taskEvent = new AutoResetEvent(false);
Task task;
// Called by the main thread.
public void StartTask(Task task) {
this.task = task;
// Signal the worker thread to perform the task.
this.taskEvent.Set();
// Return and let the task execute on another thread.
}
// Called by the worker thread.
void ThreadProc() {
while (true) {
// Wait for the event to become signaled.
this.taskEvent.WaitOne();
// Perform the task.
}
}
Vous remarquerez que l'accès à l' this.task
n'est probablement pas synchronisé correctement, que le thread n'est pas en mesure de retourner les résultats au thread principal, et qu'il n'y a aucun moyen de signaler le thread de travail à terminer. Tout cela peut être corrigé dans un exemple plus élaboré.
Impasse
Un exemple courant de blocage, c'est quand vous avez deux serrures, et vous n'êtes pas attention à la façon dont vous acquérir. À un moment vous acquérir lock1
avant lock2
:
public void f() {
lock (lock1) {
lock (lock2) {
// Do something
}
}
}
À un autre moment vous acquérir lock2
avant lock1
:
public void g() {
lock (lock2) {
lock (lock1) {
// Do something else
}
}
}
Nous allons voir comment cela peut impasse:
- enfiler les appels
f
- enfiler Une acquiert
lock1
- changement de contexte
- le fil B appels
g
- le fil B acquiert
lock2
- le fil B essaie d'acquérir
lock1
mais a plus qu'à attendre
- changement de contexte
- thread tente d'acquérir
lock2
mais a plus qu'à attendre
- changement de contexte
À ce stade, le thread A et B sont en attente pour l'autre, et sont dans l'impasse.