¿Cuándo usar el grupo de subprocesos en C #? [cerrado]


127

He estado tratando de aprender programación multiproceso en C # y estoy confundido acerca de cuándo es mejor usar un grupo de subprocesos en lugar de crear mis propios subprocesos. Un libro recomienda usar un grupo de subprocesos solo para tareas pequeñas (lo que sea que eso signifique), pero parece que no puedo encontrar ninguna guía real. ¿Cuáles son algunas consideraciones que utiliza al tomar esta decisión de programación?

Respuestas:


47

Si tiene muchas tareas lógicas que requieren un procesamiento constante y desea que se realice en paralelo, use el programador pool +.

Si necesita hacer sus tareas relacionadas con IO al mismo tiempo, como descargar cosas de servidores remotos o acceso a disco, pero necesita hacer esto una vez cada pocos minutos, luego cree sus propios hilos y elimínelos una vez que haya terminado.

Editar: sobre algunas consideraciones, utilizo grupos de subprocesos para el acceso a la base de datos, física / simulación, IA (juegos) y para tareas con script ejecutadas en máquinas virtuales que procesan muchas tareas definidas por el usuario.

Normalmente, un grupo consta de 2 subprocesos por procesador (probablemente 4 hoy en día), sin embargo, puede configurar la cantidad de subprocesos que desea, si sabe cuántos necesita.

Editar: La razón para hacer sus propios hilos es debido a los cambios de contexto (es decir, cuando los hilos necesitan intercambiarse dentro y fuera del proceso, junto con su memoria). Tener cambios de contexto inútiles, digamos que cuando no está usando sus hilos, simplemente dejarlos sentados como se podría decir, puede fácilmente reducir la mitad del rendimiento de su programa (digamos que tiene 3 hilos dormidos y 2 hilos activos). Por lo tanto, si los hilos de descarga solo están esperando, están consumiendo toneladas de CPU y enfriando el caché para su aplicación real


2
Ok, pero ¿puedes explicar por qué así es como lo enfocas? Por ejemplo, ¿cuál es la desventaja de usar el grupo de subprocesos para descargar desde servidores remotos o hacer disco IO?

8
Si un subproceso está esperando un objeto de sincronización (evento, semáforo, mutex, etc.), el subproceso no consume CPU.
Brannon

77
Como dijo Brannon, un mito común es que la creación de múltiples hilos impacta el rendimiento. En realidad, los hilos no utilizados consumen muy pocos recursos. Los cambios de contexto comienzan a ser un problema solo en servidores de muy alta demanda (en este caso, consulte los puertos de finalización de E / S para obtener una alternativa).
FDCastel

12
¿Los hilos inactivos afectan el rendimiento? Depende de cómo esperen. Si están bien escritos y esperan un objeto de sincronización, no deberían consumir recursos de la CPU. Si espera en un bucle que se despierta periódicamente para verificar los resultados, está desperdiciando la CPU. Como siempre, todo se reduce a una buena codificación.
Bill

2
Los hilos administrados inactivos comen memoria para su pila. Por defecto es 1 MiB por hilo. Por lo tanto, es mejor tener todos los hilos funcionando.
Vadym Stetsiak

48

Te sugiero que uses un grupo de subprocesos en C # por los mismos motivos que cualquier otro lenguaje.

Cuando desee limitar el número de subprocesos en ejecución o no desee la sobrecarga de crearlos y destruirlos, use un grupo de subprocesos.

Por pequeñas tareas, el libro que lee significa tareas con una vida corta. Si lleva diez segundos crear un hilo que solo se ejecuta durante un segundo, ese es un lugar donde debería usar grupos (ignore mis cifras reales, es la relación lo que cuenta).

De lo contrario, pasa la mayor parte de su tiempo creando y destruyendo hilos en lugar de simplemente hacer el trabajo que están destinados a hacer.


28

Aquí hay un buen resumen del grupo de hilos en .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

La publicación también tiene algunos puntos sobre cuándo no debe usar el grupo de hilos y comenzar su propio hilo.


8
-1 para el enlace. Estoy seguro de que es un buen enlace, pero espero que SO sea autosuficiente.
Jon Davis

26
@ stimpy77 - esa es la expectativa equivocada entonces. SO nunca puede ser autosuficiente, porque no es la máxima autoridad en todas las preguntas, ni toda la información detallada sobre cada tema puede (y debe) duplicarse en todas y cada una de las respuestas SO que tocan ese tema. (y no creo que tenga la reputación suficiente para rechazar cada respuesta de Jon Skeet solo que tiene un enlace saliente, y mucho menos todas las respuestas de todos los usuarios de SO que tienen enlaces salientes :-))
Franci Penov

2
Quizás estaba siendo demasiado sucinto, quizás debería aclararlo. No estoy en contra de los enlaces. Estoy en contra de las respuestas que contienen solo un enlace. No creo que sea una respuesta. Ahora, si se hubiera publicado un breve resumen de la respuesta para resumir cómo se aplica el contenido vinculado, eso sería aceptable. Además, vine aquí buscando una respuesta al mismo problema y esta respuesta me irritó porque era otro enlace en el que tenía que hacer clic para tener una idea de lo que podría decir en referencia al problema específico. De todos modos, ¿dónde se relaciona Jon Skeet con esto? ¿Y por qué debería importarme?
Jon Davis

8
"Llegaste a esta publicación dos años después de que se publicó y todo lo que copié aquí podría haber quedado obsoleto". Entonces podría un enlace. Publique un resumen breve pero completo cuando publique un enlace, nunca se sabe si un enlace se vuelve obsoleto o está muerto.
Jon Davis

2
No estoy de acuerdo con el estímulo: no la idea de publicaciones que contengan toneladas de información debido a la inviabilidad, ni llamar a alguien por esto. Sin embargo, diría que es más probable que un enlace deje de funcionar que el contenido quede obsoleto. Entonces, más contenido es bueno cuando la ocasión lo permite. Todos somos (en su mayoría) voluntarios, así que agradezca lo que recibe - gracias Franci :)
zanlok

14

Recomiendo leer este libro electrónico gratuito: Threading in C # por Joseph Albahari

Al menos lea la sección "Introducción". El libro electrónico ofrece una excelente introducción e incluye una gran cantidad de información avanzada sobre subprocesos.

Saber si usar o no el grupo de subprocesos es solo el comienzo. A continuación, deberá determinar qué método de ingresar al grupo de hilos se adapta mejor a sus necesidades:

  • Biblioteca paralela de tareas (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • Delegados asincrónicos
  • BackgroundWorker

Este libro electrónico explica todo esto y aconseja cuándo usarlos en lugar de crear su propio hilo.


8

El grupo de subprocesos está diseñado para reducir el cambio de contexto entre sus subprocesos. Considere un proceso que tiene varios componentes en ejecución. Cada uno de esos componentes podría estar creando hilos de trabajo. Cuantos más subprocesos haya en su proceso, más tiempo se perderá en el cambio de contexto.

Ahora, si cada uno de esos componentes fueran elementos en cola para el grupo de subprocesos, tendría mucho menos gastos generales de cambio de contexto.

El grupo de subprocesos está diseñado para maximizar el trabajo que se realiza en sus CPU (o núcleos de CPU). Es por eso que, de manera predeterminada, el grupo de subprocesos gira varios subprocesos por procesador.

Hay algunas situaciones en las que no desea utilizar el grupo de subprocesos. Si está esperando E / S, o esperando un evento, etc., entonces ata ese hilo del grupo de subprocesos y nadie más puede usarlo. La misma idea se aplica a las tareas de larga duración, aunque lo que constituye una tarea de larga duración es subjetiva.

Pax Diablo también hace un buen punto. Girar hilos no es gratis. Lleva tiempo y consumen memoria adicional para su espacio de pila. El grupo de subprocesos reutilizará los subprocesos para amortizar este costo.

Nota: preguntó sobre el uso de un subproceso de grupo de subprocesos para descargar datos o realizar E / S de disco. No debe usar un subproceso de grupo de subprocesos para esto (por las razones que describí anteriormente). En su lugar, use E / S asíncrona (también conocido como los métodos BeginXX y EndXX). Por un FileStreamque sería BeginReady EndRead. Por un HttpWebRequestque sería BeginGetResponsey EndGetResponse. Son más complicados de usar, pero son la forma correcta de realizar E / S multiproceso.


1
ThreadPool es un automatismo inteligente. "Si su cola permanece estacionaria durante más de medio segundo, responde creando más subprocesos, uno cada medio segundo, hasta la capacidad del grupo de subprocesos" ( albahari.com/threading/#_Optimizing_the_Thread_Pool ). También se utilizan operaciones casi asincrónicas con BeginXXX-EndXXX a través de ThreadPool. Por lo tanto, es normal usar ThreadPool para descargar datos y, a menudo, se usa implícitamente.
Artru

6

Tenga cuidado con el grupo de subprocesos de .NET para las operaciones que pueden bloquear cualquier parte significativa, variable o desconocida de su procesamiento, ya que es propenso a la inanición de subprocesos. Considere usar las extensiones paralelas de .NET, que proporcionan una buena cantidad de abstracciones lógicas sobre las operaciones de subprocesos. También incluyen un nuevo programador, que debería ser una mejora en ThreadPool. Ver aquí


2
¡Descubrimos esto de la manera difícil! ASP.Net usa el Threadpool aparece y, por lo tanto, no podríamos usarlo tan agresivo como nos gustaría.
noocito

3

Una razón para usar el grupo de subprocesos solo para tareas pequeñas es que hay un número limitado de subprocesos de grupo de subprocesos. Si se usa uno durante mucho tiempo, entonces detiene ese hilo de ser utilizado por otro código. Si esto sucede muchas veces, el grupo de subprocesos puede agotarse.

Usar el grupo de subprocesos puede tener efectos sutiles: algunos temporizadores .NET usan subprocesos de grupo de subprocesos y no se activarán, por ejemplo.


2

Si tiene una tarea en segundo plano que durará mucho tiempo, como toda la vida útil de su aplicación, crear su propio hilo es algo razonable. Si tiene trabajos cortos que deben realizarse en un subproceso, utilice la agrupación de subprocesos.

En una aplicación donde está creando muchos subprocesos, la sobrecarga de crear los subprocesos se vuelve sustancial. El uso del grupo de subprocesos crea los subprocesos una vez y los reutiliza, evitando así la sobrecarga de creación de subprocesos.

En una aplicación en la que trabajé, cambiar de crear subprocesos a usar el grupo de subprocesos para los subprocesos de corta duración realmente ayudó a superar la aplicación.


Aclare si quiere decir "un grupo de subprocesos" o "el grupo de subprocesos". Estas son cosas muy diferentes (al menos en el MS CLR).
bzlm

2

Para obtener el máximo rendimiento con unidades que se ejecutan simultáneamente, escriba su propio grupo de subprocesos, donde se crea un grupo de objetos de subprocesos al inicio y se pasa al bloqueo (anteriormente suspendido), esperando que se ejecute un contexto (un objeto con una interfaz estándar implementada por tu codigo).

Muchos artículos sobre Tareas frente a subprocesos frente a .NET ThreadPool no le ofrecen realmente lo que necesita para tomar una decisión de rendimiento. Pero cuando los comparas, los hilos ganan y especialmente un grupo de hilos. Se distribuyen mejor entre las CPU y se inician más rápido.

Lo que debe discutirse es el hecho de que la unidad de ejecución principal de Windows (incluido Windows 10) es un subproceso, y la sobrecarga de cambio de contexto del sistema operativo suele ser insignificante. En pocas palabras, no he podido encontrar evidencia convincente de muchos de estos artículos, ya sea que el artículo afirme un mayor rendimiento al guardar el cambio de contexto o un mejor uso de la CPU.

Ahora por un poco de realismo:

La mayoría de nosotros no necesitará que nuestra aplicación sea determinista, y la mayoría de nosotros no tenemos un fondo duro con hilos, que por ejemplo a menudo viene con el desarrollo de un sistema operativo. Lo que escribí arriba no es para un principiante.

Entonces, lo que puede ser más importante es discutir qué es fácil de programar.

Si crea su propio grupo de subprocesos, tendrá que escribir un poco, ya que deberá preocuparse por el seguimiento del estado de ejecución, cómo simular la suspensión y la reanudación, y cómo cancelar la ejecución, incluso en toda una aplicación apagar. Es posible que también deba preocuparse si desea hacer crecer dinámicamente su grupo y también qué limitación de capacidad tendrá su grupo. Puedo escribir ese marco en una hora, pero eso es porque lo he hecho muchas veces.

Quizás la forma más fácil de escribir una unidad de ejecución es usar una tarea. La belleza de una tarea es que puede crear una y ponerla en línea en su código (aunque se debe tener precaución). Puede pasar un token de cancelación para controlar cuándo desea cancelar la Tarea. Además, utiliza el enfoque de promesa para encadenar eventos, y puede hacer que devuelva un tipo específico de valor. Además, con async y wait, existen más opciones y su código será más portátil.

En esencia, es importante comprender los pros y los contras con Tareas vs. Threads vs. .NET ThreadPool. Si necesito un alto rendimiento, voy a usar hilos, y prefiero usar mi propio grupo.

Una manera fácil de comparar es iniciar 512 hilos, 512 tareas y 512 hilos de ThreadPool. Al principio encontrará un retraso con los subprocesos (por lo tanto, por qué escribir un grupo de subprocesos), pero los 512 subprocesos se ejecutarán en unos segundos mientras que los subprocesos de tareas y .NET ThreadPool tardan unos minutos en comenzar.

A continuación se muestran los resultados de dicha prueba (i5 quad core con 16 GB de RAM), que se ejecutan cada 30 segundos. El código ejecutado realiza E / S de archivo simple en una unidad SSD.

Resultados de la prueba


1
Para su información, olvidé mencionar que las tareas y los subprocesos .NET son simultaneidad concurrente dentro de .NET y que la administración se ejecuta dentro de .NET y no en el sistema operativo, siendo este último mucho más eficiente en la administración de ejecuciones concurrentes. Utilizo Tareas para muchas cosas, pero utilizo un subproceso de sistema operativo para un rendimiento de ejecución pesado. MS afirma que las tareas y los subprocesos .NET son mejores, pero en general son para equilibrar la concurrencia entre las aplicaciones .NET. Sin embargo, una aplicación de servidor funcionaría mejor permitiendo que el SO maneje la concurrencia.

Me encantaría ver la implementación de su Threadpool personalizado. Agradable escribir!
Francis

No entiendo los resultados de su prueba. ¿Qué significa das "Unidades Ran"? ¿Compara 34 taks con 512 hilos? ¿Podría explicar esto por favor?
Elmue

Unit es solo un método para ejecutar simultáneamente en un subproceso de trabajo Task, Thread o .NET ThreadPool, mi prueba compara el rendimiento de inicio / ejecución. Cada prueba tiene 30 segundos para generar 512 subprocesos desde cero, 512 tareas, 512 subprocesos de trabajo ThreadPool, o reanudar un grupo de 512 subprocesos iniciados en espera de un contexto para ejecutarse. Los subprocesos de trabajo Tareas y ThreadPool tienen un giro lento, por lo que 30 segundos no es tiempo suficiente para girarlos todos. Sin embargo, si el recuento de subprocesos de trabajo mínimo de ThreadPool se establece primero en 512, tanto los subprocesos de trabajo de Tareas como los subprocesos de trabajo de ThreadPool girarán casi tan rápido como 512 subprocesos desde cero.


1

Los grupos de subprocesos son excelentes cuando tiene que procesar más tareas que los subprocesos disponibles.

Puede agregar todas las tareas a un grupo de subprocesos y especificar el número máximo de subprocesos que se pueden ejecutar en un momento determinado.

Consulte esta página en MSDN: http://msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx


Ok, supongo que esto se relaciona con mi otra pregunta. ¿Cómo sabes cuántos hilos disponibles tienes en un momento dado?

Bueno, es difícil saberlo. Tendrás que hacer pruebas de rendimiento. Después de un punto, agregar más hilos no te dará más velocidad. Descubra cuántos procesadores hay en la máquina, ese será un buen punto de partida. Luego, suba desde allí, si la velocidad de procesamiento no mejora, no agregue más hilos.
lajos

1

Utilice siempre un grupo de subprocesos si puede, trabaje al más alto nivel de abstracción posible. Los grupos de subprocesos ocultan crear y destruir subprocesos para usted, ¡esto generalmente es algo bueno!


1

La mayoría de las veces puede usar el grupo ya que evita el costoso proceso de crear el hilo.

Sin embargo, en algunos escenarios, es posible que desee crear un hilo. Por ejemplo, si no es el único que usa el grupo de subprocesos y el subproceso que crea es de larga duración (para evitar consumir recursos compartidos) o, por ejemplo, si desea controlar el tamaño de pila del subproceso.


1

No olvides investigar al trabajador de fondo.

Encuentro muchas situaciones, me da justo lo que quiero sin el trabajo pesado.

Salud.


cuando se trata de una aplicación simple que sigue ejecutándose y tienes otra tarea que hacer, es muy fácil hacer este código. aunque no proporcionaste enlaces: especificación y tutorial
zanlok

0

Usualmente uso el Threadpool cuando necesito hacer algo en otro hilo y realmente no me importa cuándo se ejecuta o termina. Algo como el registro o tal vez incluso la descarga en segundo plano de un archivo (aunque hay mejores formas de hacerlo al estilo asíncrono). Uso mi propio hilo cuando necesito más control. También lo que he encontrado es que usar una cola Threadsafe (hackear la tuya propia) para almacenar "objetos de comando" es bueno cuando tengo varios comandos en los que necesito trabajar en> 1 hilo. Por lo tanto, puede dividir un archivo Xml y poner cada elemento en una cola y luego tener varios subprocesos trabajando en hacer un procesamiento en estos elementos. Escribí tal cola en uni (¡VB.net!) Que he convertido a C #. Lo he incluido a continuación sin ningún motivo en particular (este código puede contener algunos errores).

using System.Collections.Generic;
using System.Threading;

namespace ThreadSafeQueue {
    public class ThreadSafeQueue<T> {
        private Queue<T> _queue;

        public ThreadSafeQueue() {
            _queue = new Queue<T>();
        }

        public void EnqueueSafe(T item) {
            lock ( this ) {
                _queue.Enqueue(item);
                if ( _queue.Count >= 1 )
                    Monitor.Pulse(this);
            }
        }

        public T DequeueSafe() {
            lock ( this ) {
                while ( _queue.Count <= 0 )
                    Monitor.Wait(this);

                return this.DeEnqueueUnblock();

            }
        }

        private T DeEnqueueUnblock() {
            return _queue.Dequeue();
        }
    }
}

Algunos problemas con este enfoque: - Las llamadas a DequeueSafe () esperarán hasta que un elemento sea EnqueuedSafe (). Considere usar una de las sobrecargas Monitor.Wait () que especifique un tiempo de espera. - Bloquear esto no está de acuerdo con las mejores prácticas, sino que crea un campo de objeto de solo lectura. - Aunque Monitor.Pulse () es liviano, llamarlo cuando la cola contiene solo 1 elemento sería más eficiente. - DeEnqueueUnblock () debería verificar preferiblemente la cola. Conteo> 0. (necesario si se usa Monitor.PulseAll o esperar tiempos de espera)
Craig Nicholson

0

Quería un grupo de subprocesos para distribuir el trabajo entre los núcleos con la menor latencia posible, y eso no tenía que funcionar bien con otras aplicaciones. Descubrí que el rendimiento del grupo de subprocesos .NET no era tan bueno como podría ser. Sabía que quería un hilo por núcleo, así que escribí mi propia clase de substituto de grupo de hilos. El código se proporciona como respuesta a otra pregunta de StackOverflow aquí .

En cuanto a la pregunta original, el grupo de subprocesos es útil para dividir los cálculos repetitivos en partes que se pueden ejecutar en paralelo (suponiendo que se puedan ejecutar en paralelo sin cambiar el resultado). La gestión manual de subprocesos es útil para tareas como IU e IO.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.