Pregunta

He intentado aprender programación de subprocesos múltiples 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 pautas reales. ¿Cuáles son algunas consideraciones que utiliza al tomar esta decisión de programación?

¿Fue útil?

Solución

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

Si necesita realizar las tareas relacionadas con IO de forma simultánea, como descargar material de servidores remotos o acceso a discos, pero debe hacer lo siguiente cada pocos minutos, luego cree sus propios hilos y elimínelos una vez que haya terminado.

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

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

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

Otros consejos

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 conjunto de subprocesos.

Por tareas pequeñas, el libro que lees significa tareas con una vida útil corta. Si tarda diez segundos en crear un hilo que solo se ejecuta durante un segundo, ese es un lugar donde deberías usar grupos (ignorar mis cifras reales, es la proporción que cuenta).

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

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 debes usar el grupo de hilos y comenzar tu propio hilo en su lugar.

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

Al menos lee la sección " Getting Started " sección. El libro electrónico ofrece una excelente introducción e incluye una gran cantidad de información de subprocesos avanzados también.

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

  • Biblioteca paralela de tareas (.NET Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • Delegados asíncronos
  • BackgroundWorker

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

El grupo de subprocesos está diseñado para reducir el cambio de contexto entre los 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 pusiera elementos en 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 hace girar múltiples subprocesos por procesador.

Hay algunas situaciones en las que no desea utilizar el grupo de subprocesos. Si está esperando una E / S, o está esperando un evento, etc., entonces está atando ese hilo de la agrupación de hilos y nadie más puede utilizarlo. La misma idea se aplica a las tareas de ejecución prolongada, aunque lo que constituye una tarea de ejecución prolongada es subjetivo.

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: usted preguntó sobre el uso de un subproceso de grupo de subprocesos para descargar datos o realizar E / S de disco. No debe utilizar un subproceso de grupo de subprocesos para esto (por las razones que he descrito anteriormente). En su lugar, utilice E / S asíncrona (también conocido 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.

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

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

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

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 realizarse en un subproceso, utilice la agrupación de subprocesos.

En una aplicación en la que 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 conjunto de subprocesos para los subprocesos de corta duración realmente ayudó a la aplicación de la aplicación.

Para obtener el mayor rendimiento con unidades que se ejecutan simultáneamente, escriba su propio grupo de subprocesos, donde se crea un grupo de objetos Thread al inicio y se dirige al bloqueo (anteriormente suspendido), esperando que se ejecute un contexto (un objeto con un estándar interfaz implementada por su código).

Tantos artículos sobre Tasks vs. Threads vs. .NET ThreadPool no le dan realmente lo que necesita para tomar una decisión de rendimiento. Pero cuando los comparas, Threads gana y especialmente un grupo de Threads. Se distribuyen de la mejor manera entre las CPU y se inician más rápido.

Lo que se debe discutir es el hecho de que la unidad de ejecución principal de Windows (incluido Windows 10) es un subproceso, y la sobrecarga de conmutación 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 reclame un mayor rendimiento al guardar el cambio de contexto o un mejor uso de la CPU.

Ahora para un poco de realismo:

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

Entonces, lo 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 tendrá que preocuparse por el seguimiento del estado de ejecución, cómo simular la suspensión y reanudación, y cómo cancelar la ejecución, incluso en una toda la aplicación se apaga. Es posible que también deba preocuparse por si desea aumentar 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 lanzarla en línea en su código (aunque es posible que tenga precaución). Puede pasar un token de cancelación para manejarlo 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 async y esperar, 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 contra Hilos frente a .NET ThreadPool. Si necesito un alto rendimiento, usaré subprocesos y prefiero usar mi propio grupo.

Una forma fácil de comparar es iniciar 512 subprocesos, 512 tareas y 512 subprocesos ThreadPool. Encontrará un retraso al principio con los subprocesos (por lo tanto, por qué escribir un grupo de subprocesos), pero todos los 512 subprocesos se ejecutarán en unos pocos segundos, mientras que los subprocesos de Tareas y .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) y cada 30 segundos de ejecución. El código ejecutado realiza una E / S de archivo simple en una unidad SSD.

Resultados de la prueba

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

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

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

Siempre que sea posible, utilice un grupo de subprocesos, trabaje al nivel más alto de abstracción posible. Los grupos de subprocesos se esconden creando y destruyendo hilos para ti, ¡esto suele ser algo bueno!

La mayoría de las veces puedes usar el grupo para evitar 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 el consumo de recursos compartidos) o, por ejemplo, si desea controlar el tamaño de pila del subproceso.

No te olvides de investigar al trabajador de fondo.

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

Saludos.

Normalmente uso Threadpool cuando necesito hacer algo en otro hilo y no me importa cuando se ejecuta o finaliza. 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). Utilizo mi propio hilo cuando necesito más control. Además, lo que he encontrado es 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 el procesamiento de estos elementos. Escribí una cola tan atrás en uni (VB.net!) Que he convertido a C #. Lo he incluido a continuación sin ninguna razón 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();
        }
    }
}

Quería que un grupo de subprocesos distribuyera el trabajo entre los núcleos con la menor latencia posible, y eso no tenía que jugar bien con otras aplicaciones. Encontré 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 sustituto 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 pueden ejecutarse en paralelo (asumiendo que pueden ejecutarse en paralelo sin cambiar el resultado). La administración manual de subprocesos es útil para tareas como UI e IO

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top