Je suis un développeur junior travaillant sur l'écriture d'une mise à jour pour un logiciel qui reçoit des données d'une solution tierce, les stocke dans une base de données, puis conditionne les données pour une utilisation par une autre solution tierce. Notre logiciel fonctionne comme un service Windows.
En regardant le code d'une version précédente, je vois ceci:
static Object _workerLocker = new object();
static int _runningWorkers = 0;
int MaxSimultaneousThreads = 5;
foreach(int SomeObject in ListOfObjects)
{
lock (_workerLocker)
{
while (_runningWorkers >= MaxSimultaneousThreads)
{
Monitor.Wait(_workerLocker);
}
}
// check to see if the service has been stopped. If yes, then exit
if (this.IsRunning() == false)
{
break;
}
lock (_workerLocker)
{
_runningWorkers++;
}
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
}
La logique semble claire: attendez de la place dans le pool de threads, assurez-vous que le service n'a pas été arrêté, puis incrémentez le compteur de threads et mettez le travail en file d'attente. _runningWorkers
est décrémenté à l' SomeMethod()
intérieur d'une lock
instruction qui appelle ensuite Monitor.Pulse(_workerLocker)
.
Ma question est:
Y a - t-il un avantage à regrouper tout le code dans un seul lock
, comme ceci:
static Object _workerLocker = new object();
static int _runningWorkers = 0;
int MaxSimultaneousThreads = 5;
foreach (int SomeObject in ListOfObjects)
{
// Is doing all the work inside a single lock better?
lock (_workerLocker)
{
// wait for room in ThreadPool
while (_runningWorkers >= MaxSimultaneousThreads)
{
Monitor.Wait(_workerLocker);
}
// check to see if the service has been stopped.
if (this.IsRunning())
{
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
_runningWorkers++;
}
else
{
break;
}
}
}
Il semble que cela puisse causer un peu plus d'attente pour d'autres threads, mais il semble alors que le verrouillage à plusieurs reprises dans un seul bloc logique prendrait également un peu de temps. Cependant, je suis nouveau dans le multi-threading, donc je suppose qu'il y a d'autres problèmes ici que je ne connais pas.
Le seul autre endroit où _workerLocker
est verrouillé est à l' intérieur SomeMethod()
, et uniquement dans le but de décrémenter _runningWorkers
, puis à l'extérieur de l' foreach
attente du nombre de _runningWorkers
zéro pour se connecter et revenir.
Merci pour toute aide.
EDIT 4/8/15
Merci à @delnan pour la recommandation d'utiliser un sémaphore. Le code devient:
static int MaxSimultaneousThreads = 5;
static Semaphore WorkerSem = new Semaphore(MaxSimultaneousThreads, MaxSimultaneousThreads);
foreach (int SomeObject in ListOfObjects)
{
// wait for an available thread
WorkerSem.WaitOne();
// check if the service has stopped
if (this.IsRunning())
{
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
}
else
{
break;
}
}
WorkerSem.Release()
est appelé à l'intérieur SomeMethod()
.
SomeMethod
manière asynchrone, la section "lock" ci-dessus sera laissée avant ou au moins peu de temps après l'exécution du nouveau thread avec SomeMethod
.
Monitor.Wait()
est de libérer et de réacquérir le verrou afin qu'une autre ressource ( SomeMethod
, dans ce cas) puisse l'utiliser. À l'autre extrémité, SomeMethod
obtient le verrou, décrémente le compteur, puis appelle Monitor.Pulse()
ce qui renvoie le verrou à la méthode en question. Encore une fois, c'est ma propre compréhension.
Monitor.Wait
libère le verrou. Je recommande de jeter un œil aux documents.