2008-09-28 12 views
122

He estado tratando de aprender la programación de subprocesos múltiples en C# y estoy confundido acerca de cuándo es mejor utilizar un grupo de subprocesos contra 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 de las consideraciones que usa al tomar esta decisión de programación?Cuándo usar el grupo de subprocesos en C#?

Respuesta

44

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

Si necesita realizar sus tareas relacionadas con IO al mismo tiempo, como descargar cosas de servidores remotos o acceder al disco, pero debe hacerlo una vez cada pocos minutos, luego cree sus propios hilos y mátalos una vez que haya terminado.

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

Normalmente, un grupo consta de 2 subprocesos por procesador (tan probable 4 actualmente), sin embargo, puede configurar la cantidad de subprocesos que desee, si sabe cuántos necesita.

Editar: La razón para hacer sus propios hilos es debido a los cambios de contexto, (eso es cuando los hilos necesitan intercambiar y fuera del proceso, junto con su memoria). Tener cambios de contexto inútiles, por ejemplo, cuando no está utilizando sus hilos, simplemente dejándolos sentados como podría decirse, puede fácilmente la mitad del rendimiento de su programa (digamos que tiene 3 hilos que duermen y 2 hilos activos). Por lo tanto, si esos subprocesos de descarga están esperando están consumiendo toneladas de CPU y enfriando la caché para su aplicación real

+2

Bien, pero ¿puede explicar por qué esta es la forma de abordarlo? Por ejemplo, ¿cuál es la desventaja de usar el grupo de subprocesos para descargar desde servidores remotos o hacer un disco IO? –

+7

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

+7

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

1

Los grupos de subprocesos son geniales 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.

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

+0

bien supongo que esto se vincula a mi otra pregunta. ¿Cómo sabe cuántos hilos disponibles tiene en un momento dado? –

+0

Bueno, es difícil de decir. Tendrás que hacer pruebas de rendimiento. Después de un punto agregar más hilos no le dará más velocidad. Averigüe 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

Use siempre un grupo de subprocesos si es posible, el trabajo al más alto nivel de abstracción posible. Las piscinas de subprocesos se ocultan creando y destruyendo hilos para ti, ¡esto suele ser algo bueno!

47

Le sugiero que use un grupo de subprocesos en C# por las mismas razones que en cualquier otro idioma.

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.

Mediante tareas pequeñas, el libro que lee significa tareas de corta duración. Si lleva diez segundos crear un hilo que solo se ejecuta durante un segundo, ese es un lugar donde deberías usar pools (ignora mis cifras reales, es la proporción lo que cuenta).

De lo contrario, usted 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í es un buen resumen de la agrupación de hebras en .Net: http://blogs.msdn.com/pedram/archive/2007/08/05/dedicated-thread-or-a-threadpool-thread.aspx

El post también tiene algunos puntos acerca de cuándo no se debe utilizar el grupo de subprocesos y comenzar su propio hilo en su lugar.

+7

-1 para el enlace. Estoy seguro de que es un buen enlace, pero espero que sea autosuficiente. –

+26

@ stimpy77 - esa es la expectativa incorrecta 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 debería) duplicarse en todas y cada una de las respuestas SO que tocan ese tema. (y no creo que tengas la reputación suficiente para rechazar cada una de las respuestas de Jon Skeet solo que tiene un enlace de salida, y mucho menos todas las respuestas de todos los usuarios de SO que tienen enlaces salientes :-)) –

+2

Quizás estaba siendo excesivamente sucinta, tal vez debería aclarar. 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í en busca de una respuesta al mismo problema y esta respuesta me irritó porque era otro enlace al 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? –

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 subprocesos de trabajo. Cuantos más hilos haya en su proceso, más tiempo se desperdicia en el cambio de contexto.

Ahora, si cada uno de esos componentes estuviera haciendo cola en el grupo de subprocesos, tendría mucho menos sobrecarga 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 forma predeterminada, el grupo de subprocesos genera múltiples subprocesos por procesador.

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

Pax Diablo hace un buen punto también. Spinning Up Threads no es gratis. Lleva tiempo y consumen memoria adicional para su espacio de pila. El grupo de subprocesos volverá a usar subprocesos para amortizar este costo.

Nota: ha preguntado sobre el uso de un subproceso de grupo de subprocesos para descargar datos o realizar E/S de disco. No debe usar un hilo de grupo de subprocesos para esto (por las razones que describí anteriormente). En su lugar, use E/S asincrónicas (también conocidas como los métodos BeginXX y EndXX). Para un FileStream que sería BeginRead y EndRead. Para un HttpWebRequest que sería BeginGetResponse y EndGetResponse. Son más complicados de usar, pero son la forma correcta de realizar E/S de múltiples subprocesos.

+1

ThreadPool es una automatización 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" (http://www.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

1

La mayoría de las veces puede usar el grupo de servidores para evitar el costoso proceso de crear el hilo.

Sin embargo, en algunos casos es posible que desee crear un hilo. Por ejemplo, si usted 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.

3

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

El uso del conjunto de subprocesos puede tener efectos sutiles: algunos temporizadores .NET utilizan subprocesos de grupo de subprocesos y no se activarán, por ejemplo.

1

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

En una aplicación en la que está creando muchos subprocesos, la sobrecarga de la creación de 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é, el cambio de la creación de subprocesos al uso del grupo de subprocesos para los subprocesos de corta duración realmente ayudó a poner en práctica la aplicación.

+0

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

6

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

+2

¡Descubrimos esto de la manera difícil! ASP.Net utiliza el Threadpool is aparece, por lo que no podríamos usarlo de manera tan agresiva como nos gustaría. – noocyte

0

Normalmente uso Threadpool cada vez que necesito hacer algo en otro hilo y realmente no me importa cuándo se ejecuta o termina. Algo así como iniciar sesión o incluso descargar en segundo plano un archivo (aunque hay mejores formas de hacerlo asincrónico). Uso mi propio hilo cuando necesito más control. También lo que he descubierto es que usar una cola de Threadsafe (hackear la suya) para almacenar "objetos de comando" es agradable cuando tengo varios comandos en los que necesito trabajar en> 1 hilo. Por lo tanto, podría dividir un archivo Xml y poner cada elemento en una cola y luego tener varios hilos trabajando en hacer algún procesamiento en estos elementos. Escribí una cola así en la universidad (¡VB.net!) Que convertí 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(); 
     } 
    } 
} 
+0

Algunos problemas con este enfoque: - Las llamadas a DequeueSafe() esperarán hasta que un elemento esté EnqueuedSafe(). Considere usar una de las sobrecargas Monitor.Wait() especificando un tiempo de espera. - Bloquear esto no está de acuerdo con las mejores prácticas, sino crear un campo de objetos de solo lectura. - Aunque Monitor.Pulse() es liviano, llamándolo cuando la cola contiene solo 1 elemento sería más eficiente. - DeEnqueueUnblock() debería comprobar preferiblemente la cola.Count> 0. (necesario si se usan Monitor.PulseAll o wait timeouts) –

1

No olvide investigar al trabajador de segundo plano.

Encuentro para muchas situaciones, me da exactamente lo que quiero sin levantar objetos pesados.

Saludos.

+0

cuando es una aplicación simple que se ejecuta y tiene otra tarea que hacer, muy fácil de hacer esto código. Aunque no proporcionó enlaces: [especificación] (http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx) y [tutorial] (http://dotnetperls.com/backgroundworker) – zanlok

0

Quería un grupo de subprocesos para distribuir el trabajo entre los núcleos con la menor latencia posible, y que 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 sustituta del conjunto de subprocesos. El código se proporciona como respuesta a otra pregunta de StackOverflow over here.

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

14

Te recomiendo la lectura del este e-libro libre: Threading in C# by Joseph Albahari

Al menos lea la sección "Introducción". El e-book ofrece una excelente introducción e incluye una gran cantidad de información avanzada sobre enhebrado.

Saber si usar o no el grupo de subprocesos es solo el comienzo. A continuación, tendrá que determinar qué método de entrar en el grupo de subprocesos mejor se adapte a sus necesidades:

  • tarea paralela Library (.NET Framework 4.0 )
  • ThreadPool.QueueUserWorkItem
  • asincrónicos delegados
  • BackgroundWorker

Este e-book explica todo esto y aconseja cuándo usarlos frente a crear su propio hilo.

1

Para obtener el rendimiento más alto con unidades de ejecución simultáneas, escriba su propio grupo de subprocesos, donde se crean un grupo de objetos de subproceso al inicio y vaya a bloqueo (anteriormente suspendido), esperando que se ejecute un contexto (un objeto con un interfaz estándar implementada por su código).

Muchos artículos acerca de Tareas vs. Hilos en comparación con .NET ThreadPool no logran darle realmente lo que necesita para tomar una decisión sobre el rendimiento. Pero cuando los compara, 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 hilo, y la sobrecarga de cambio de contexto del sistema operativo suele ser despreciable. En pocas palabras, no he podido encontrar evidencia convincente de muchos de estos artículos, ya sea que el artículo reclame un mayor rendimiento al guardar el cambio de contexto o un mejor uso de la CPU.

Ahora un poco de realismo:

La mayoría de nosotros no va a necesitar nuestra aplicación para ser determinista, y la mayoría de nosotros no posee un historial de golpes duros con hilos, que, por ejemplo, a menudo viene con el desarrollo un sistema operativo. Lo que escribí arriba no es para principiantes.

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

Si crea su propio grupo de subprocesos, tendrá que escribir un poco, ya que tendrá que preocuparse por el seguimiento del estado de ejecución, cómo simular suspender y reanudar, y cómo cancelar la ejecución, incluso en un cierre de toda la aplicación. También podría tener que preocuparse de si desea aumentar dinámicamente su grupo y también qué limitación de capacidad tendrá su grupo. Puedo escribir un marco así 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 sea utilizar una Tarea. La belleza de una Tarea es que puedes crear una y ponerla en línea en tu código (aunque puede justificarse la precaución). Puede pasar un token de cancelación para que lo maneje cuando desee cancelar la Tarea. Además, utiliza el enfoque prometedor para encadenar eventos y puede hacer que devuelva un tipo específico de valor. Además, con asincronización y espera, existen más opciones y su código será más portátil.

En esencia, es importante comprender los pros y los contras de Tareas vs. Hilos 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. Encontrará un retraso al principio con Threads (de ahí, por qué escribir un grupo de threads), pero todos los 512 Threads se ejecutarán en unos pocos segundos, mientras que las Tareas y los Threads de .NET ThreadPool tardan unos minutos en comenzar.

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

Test Results

+0

Para su información, se olvidó de mencionar que las Tareas y los hilos .NET son concurrencia simulada dentro de .NET y que la administración se ejecuta dentro de .NET, no del sistema operativo, siendo este último mucho más eficiente en la administración de ejecuciones concurrentes. Uso Tareas para muchas cosas, pero utilizo un subproceso del sistema operativo para un alto rendimiento de ejecución. MS afirma que las tareas y los hilos .NET son mejores, pero en general sirven 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. – Grabe

Cuestiones relacionadas