Sono uno sviluppatore junior che lavora alla stesura di un aggiornamento per software che riceve dati da una soluzione di terze parti, lo memorizza in un database e quindi ne condiziona l'utilizzo da parte di un'altra soluzione di terze parti. Il nostro software funziona come un servizio di Windows.
Guardando il codice di una versione precedente, vedo questo:
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 logica sembra chiara: attendere lo spazio nel pool di thread, assicurarsi che il servizio non sia stato arrestato, quindi incrementare il contatore dei thread e accodare il lavoro. _runningWorkers
è decrementato all'interno di SomeMethod()
all'interno di un'istruzione lock
che chiama quindi Monitor.Pulse(_workerLocker)
.
La mia domanda è:
C'è qualche vantaggio nel raggruppare tutto il codice all'interno di un singolo lock
, come questo:
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;
}
}
}
Sembra, potrebbe causare un po 'più di attesa per altri thread, ma poi sembra che il blocco ripetuto in un singolo blocco logico sia anche un po' dispendioso in termini di tempo. Tuttavia, sono nuovo nel multi-threading, quindi presumo che ci siano altre preoccupazioni di cui non sono a conoscenza.
Gli unici altri posti in cui _workerLocker
viene bloccato è in SomeMethod()
, e solo allo scopo di diminuire _runningWorkers
e quindi all'esterno di foreach
per attendere il numero di _runningWorkers
per passare a zero prima di accedere e tornare.
Grazie per l'aiuto.
EDIT 4/8/15
Grazie a @delnan per la raccomandazione di utilizzare un semaforo. Il codice diventa:
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()
è chiamato all'interno di SomeMethod()
.