Sono uno sviluppatore junior che lavora alla stesura di un aggiornamento per software che riceve dati da una soluzione di terze parti, li archivia in un database e quindi li condiziona per l'utilizzo da parte di un'altra soluzione di terze parti. Il nostro software funziona come un servizio 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 mettere in coda il lavoro. _runningWorkers
viene decrementato all'interno SomeMethod()
all'interno di una lock
dichiarazione che le chiamate poi Monitor.Pulse(_workerLocker)
.
La mia domanda è:
c'è qualche vantaggio nel raggruppare tutto il codice in un singolo lock
, in questo modo:
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 che potrebbe causare un po 'più di attesa per altri thread, ma poi sembra che il blocco ripetuto in un singolo blocco logico richiederebbe un po' di tempo. Tuttavia, sono nuovo del multi-threading, quindi presumo che ci siano altre preoccupazioni di cui non sono a conoscenza.
Gli unici altri posti in cui _workerLocker
viene bloccato sono dentro SomeMethod()
, e solo allo scopo di decrementare _runningWorkers
, e quindi fuori foreach
dall'attesa che il numero di _runningWorkers
vada a zero prima di accedere e tornare.
Grazie per qualsiasi aiuto.
MODIFICA 4/8/15
Grazie a @delnan per la raccomandazione di usare 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()
si chiama dentro SomeMethod()
.
SomeMethod
modo asincrono, la sezione "blocco" sopra verrà lasciata prima o almeno poco dopo l' SomeMethod
inizio del nuovo thread con inizio.
Monitor.Wait()
è quello di rilasciare e riacquisire il blocco in modo che un'altra risorsa ( SomeMethod
, in questo caso) possa utilizzarlo. Dall'altro lato, SomeMethod
ottiene il blocco, decrementa il contatore e quindi chiama Monitor.Pulse()
che restituisce il blocco al metodo in questione. Ancora una volta, questa è la mia comprensione.
Monitor.Wait
rilascia il lucchetto. Consiglio di dare un'occhiata ai documenti.