Soy un desarrollador junior que trabaja en la redacción de una actualización para el software que recibe datos de una solución de terceros, los almacena en una base de datos y luego condiciona los datos para su uso por otra solución de terceros. Nuestro software se ejecuta como un servicio de Windows.
Mirando el código de una versión anterior, veo esto:
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 lógica parece clara: espere espacio en el grupo de subprocesos, asegúrese de que el servicio no se haya detenido, luego incremente el contador de subprocesos y ponga en cola el trabajo. _runningWorkers
se decrementa dentro de SomeMethod()
una lock
declaración que luego llama Monitor.Pulse(_workerLocker)
.
Mi pregunta es:
¿hay algún beneficio en agrupar todo el código dentro de un único lock
, como este:
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;
}
}
}
Parece que puede causar un poco más de espera para otros subprocesos, pero luego parece que bloquear repetidamente en un solo bloque lógico también requeriría algo de tiempo. Sin embargo, soy nuevo en subprocesos múltiples, por lo que supongo que hay otras preocupaciones aquí que desconozco.
Los únicos otros lugares donde _workerLocker
se bloquea es adentro SomeMethod()
, y solo con el propósito de disminuir _runningWorkers
, y luego afuera foreach
para esperar a que el número _runningWorkers
llegue a cero antes de iniciar sesión y regresar.
Gracias por cualquier ayuda.
EDITAR 4/8/15
Gracias a @delnan por la recomendación de usar un semáforo. El código se convierte en:
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()
se llama adentro SomeMethod()
.
SomeMethod
forma asincrónica, la sección "bloqueo" anterior se dejará antes o al menos poco después de que SomeMethod
comience a ejecutarse el nuevo hilo .
Monitor.Wait()
es liberar y volver a adquirir el bloqueo para que otro recurso ( SomeMethod
en este caso) pueda usarlo. En el otro extremo, SomeMethod
obtiene el bloqueo, disminuye el contador y luego llama, lo Monitor.Pulse()
que devuelve el bloqueo al método en cuestión. De nuevo, este es mi propio entendimiento.
Monitor.Wait
libera el bloqueo. Recomiendo echar un vistazo a los documentos.