Ich bin ein Junior-Entwickler, der an der Erstellung eines Updates für Software arbeitet, die Daten von einer Drittanbieterlösung empfängt, diese in einer Datenbank speichert und dann die Daten für die Verwendung durch eine andere Drittanbieterlösung aufbereitet. Unsere Software wird als Windows-Dienst ausgeführt.
Wenn ich mir den Code einer früheren Version ansehe, sehe ich Folgendes:
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);
}
Die Logik scheint klar zu sein: Warten Sie auf Platz im Thread-Pool, stellen Sie sicher, dass der Dienst nicht gestoppt wurde, erhöhen Sie dann den Thread-Zähler und stellen Sie die Arbeit in eine Warteschlange. _runningWorkers
wird SomeMethod()
innerhalb einer lock
Anweisung dekrementiert , die dann aufruft Monitor.Pulse(_workerLocker)
.
Meine Frage ist:
Gibt es irgendeinen Vorteil, wenn man den gesamten Code in einer einzigen gruppiert lock
, wie folgt :
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;
}
}
}
Es scheint, als würde ein bisschen mehr auf andere Threads gewartet, aber dann scheint das wiederholte Sperren in einem einzelnen logischen Block auch etwas zeitaufwändig zu sein. Ich bin jedoch neu in Multithreading und gehe daher davon aus, dass es hier andere Bedenken gibt, die mir nicht bewusst sind.
Die einzigen anderen Orte, an denen _workerLocker
gesperrt wird, befinden sich in SomeMethod()
und nur zum Zweck des Dekrementierens _runningWorkers
, und dann außerhalb der foreach
, um zu warten, bis die Anzahl _runningWorkers
Null erreicht ist, bevor Sie sich anmelden und zurückkehren.
Vielen Dank für jede Hilfe.
EDIT 4/8/15
Vielen Dank an @delnan für die Empfehlung, ein Semaphor zu verwenden. Der Code wird:
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()
heißt drinnen SomeMethod()
.
SomeMethod
asynchron auf. Der obige Abschnitt "lock" wird vor oder zumindest kurz nach dem SomeMethod
Start des neuen Threads mit ausgeführt.
Monitor.Wait()
, die Sperre freizugeben und erneut zu erwerben, damit eine andere Ressource ( SomeMethod
in diesem Fall) sie verwenden kann. Ruft am anderen Ende SomeMethod
die Sperre ab, dekrementiert den Zähler und ruft dann auf, Monitor.Pulse()
wodurch die Sperre an die betreffende Methode zurückgegeben wird. Auch dies ist mein eigenes Verständnis.
Monitor.Wait
gibt die Sperre frei. Ich empfehle, einen Blick in die Dokumentation zu werfen.