Grupo de subprocesos (ThreadPool)

Grupo de subprocesos (ThreadPool)

Descripción general del grupo de subprocesos

Un contenedor mantenido por el sistema para albergar subprocesos, compartido por todos los AppDomains controlados por CLR. El grupo de subprocesos se puede usar para ejecutar tareas, enviar elementos de trabajo, manejar E/S asincrónicas, esperar en nombre de otros subprocesos y manejar temporizadores.

Grupo de subprocesos e hilos

  • Rendimiento: cada vez que se abre un hilo nuevo, consume espacio de memoria y recursos (alrededor de 1
    MB de memoria de forma predeterminada). El propósito del grupo de subprocesos es reducir los recursos consumidos al abrir nuevos subprocesos (usar los subprocesos inactivos en el grupo de subprocesos, no es necesario abrir nuevos subprocesos y administrar los subprocesos de manera uniforme (después de ejecutar los subprocesos en el grupo de subprocesos, volver a el grupo de subprocesos y esperar nuevos) Tarea)).
  • Tiempo: cada vez que se inicia un subproceso, lleva tiempo (cientos de milisegundos) crear un nuevo montón de variable local. El grupo de subprocesos crea previamente un conjunto de subprocesos reciclables, por lo que el tiempo de sobrecarga se puede acortar.
  • Desventajas del grupo de subprocesos: la pérdida de rendimiento del grupo de subprocesos es mejor que la de los subprocesos (lo que se logra al compartir y reciclar subprocesos), pero:

1. El grupo de subprocesos no admite operaciones interactivas como la cancelación de subprocesos, la finalización y la notificación de errores.

2. El grupo de subprocesos no admite la secuencia de ejecución de subprocesos.

3. No se puede establecer el nombre del subproceso agrupado (subproceso en el grupo de subprocesos), lo que aumentará la dificultad de la depuración del código.

4. Los subprocesos agrupados suelen ser subprocesos en segundo plano con una prioridad de ThreadPriority.Normal.

5. El bloqueo de subprocesos agrupados puede afectar el rendimiento (el bloqueo hará que CLR piense erróneamente que está usando una gran cantidad de CPU. CLR puede detectar o compensar (inyectar más subprocesos en el grupo), pero esto puede hacer que el grupo de subprocesos esté sujeto a la impresión de sobrecarga posterior. La tarea soluciona este problema).

6. El grupo de subprocesos utiliza una cola global y los subprocesos de la cola global seguirán compitiendo por los recursos compartidos, lo que afectará el rendimiento (Task resuelve este problema mediante el uso de una cola local).

Cómo funciona el grupo de subprocesos

  • Cuando se inicializa CLR, no hay subprocesos en el grupo de subprocesos.
  • Internamente, el grupo de subprocesos mantiene una cola de solicitudes de operación.
  • Cuando la aplicación realiza una operación asincrónica, se agrega una entrada a la cola del grupo de subprocesos.
  • El código del grupo de subprocesos lee registros de esta cola y envía el elemento de registro a un subproceso del grupo de subprocesos.
  • Si no hay ningún subproceso en el grupo de subprocesos, se crea un nuevo subproceso.
  • Cuando el subproceso del grupo de subprocesos termine su trabajo, el subproceso no se destruirá. En cambio, el subproceso volverá al grupo de subprocesos y entrará en un estado inactivo allí, esperando responder a otra solicitud. Dado que el subproceso no se destruye a sí mismo, no se se generará pérdida de rendimiento.
  • El programa envía múltiples solicitudes al grupo de subprocesos, y el grupo de subprocesos intenta usar solo un subproceso para atender todas las solicitudes. Cuando la velocidad de la solicitud excede la velocidad de la tarea de procesamiento de subprocesos del grupo de subprocesos, se crearán subprocesos adicionales, por lo que el grupo de subprocesos no necesidad de crear una gran cantidad de subprocesos.
  • Si deja de enviar tareas al grupo de subprocesos, una gran cantidad de subprocesos inactivos en el grupo se activarán y finalizarán solos después de un período de tiempo para liberar recursos (diferentes versiones de CLR tienen diferentes definiciones para este evento).

Subprocesos de trabajo y subprocesos de E/S

El grupo de subprocesos permite que los subprocesos programen tareas en múltiples núcleos de CPU, de modo que varios subprocesos puedan funcionar simultáneamente, utilizando así de manera eficiente los recursos del sistema y mejorando el rendimiento del programa.

El grupo de subprocesos CLR se divide en subprocesos de trabajo y subprocesos de E/S:

  • Subprocesos de trabajo (workerThreads): responsables de administrar la operación de los objetos internos de CLR y proporcionar "capacidades informáticas", por lo que generalmente se usan para operaciones vinculadas a la computación.
  • Subproceso de E/S (completionPortThreads): se utiliza principalmente para intercambiar información con sistemas externos (como leer un archivo) y distribuir devoluciones de llamada en IOCP.

Nota: El grupo de subprocesos almacenará previamente en caché algunos subprocesos de trabajo porque crear nuevos subprocesos es costoso.

Puerto de terminación de E/S (IOCP)

  • Puerto de finalización de E/S (IOCP, puerto de finalización de E/S):

IOCP es una API de E/S asíncrona (puede considerarse como una cola de mensajes), que proporciona un modelo de subprocesos para procesar múltiples solicitudes de E/S asíncronas y puede notificar de manera eficiente a las aplicaciones sobre eventos de E/S. El CLR mantiene internamente el IOCP. Cuando se completa la solicitud de E/S asíncrona, el controlador del dispositivo generará un paquete de solicitud de E/S (IRP, paquete de solicitud de E/S) y lo pondrá en cola (primero en entrar, primero en salir) en el puerto de finalización. . Después de eso, el subproceso de E/S extraerá y completará el IRP y llamará al delegado anterior.

  • Hilo de E/S, IOCP e IRP:

Al realizar operaciones de E/S (operaciones de E/S sincrónicas y operaciones de E/S asincrónicas), se llamará al método API de Windows para convertir el subproceso actual del modo de usuario al modo kernel y, al mismo tiempo, generar e inicializar un I Paquete de solicitud / O. El paquete de solicitud contiene un identificador de archivo, un desplazamiento y una matriz Byte[]. La operación de E/S transmite un paquete de solicitud al kernel. De acuerdo con el paquete de solicitud, el kernel de Windows confirma a qué dispositivo de hardware corresponde la operación de E/S. Estas operaciones de E/S ingresan en la propia cola de procesamiento del dispositivo, que es mantenida por el controlador del dispositivo.

Si se trata de una operación de E/S síncrona, entonces cuando el dispositivo de hardware está operando E/S, Windows pone el subproceso que emitió la solicitud de E/S en un estado de suspensión debido a la "espera" (procesamiento de tareas no tripulado). el dispositivo de hardware completa la operación. Vuelva a activar este hilo. Por lo tanto, el rendimiento no es alto.Si la cantidad de solicitudes es grande, la cantidad de subprocesos dormidos también será grande, lo que desperdicia muchos recursos.

Si se trata de una operación de E/S asincrónica (en .Net, las operaciones de E/S asincrónicas comienzan con Beginxxx y la implementación interna es ThreadPool.BindHandle), debe pasar una comisión, que se pasará junto con el IRP al controlador del dispositivo), este método regresa después de que Windows envía el paquete de solicitud de E/S a la cola de procesamiento del dispositivo. Al mismo tiempo, CLR asignará un subproceso disponible para continuar con la ejecución de la siguiente tarea. Cuando se complete la tarea, le recordará a CLR que su trabajo se ha completado a través de IOCP. Después de recibir la notificación, el delegado se colocará en el Cola de grupo de subprocesos CLR por subproceso I\O para realizar una devolución de llamada.

Entonces: la mayoría de las veces, los desarrolladores usan subprocesos de trabajo, y CLR invoca los subprocesos de E/S (que los desarrolladores no usan directamente).

Grupo de subprocesos básico y subproceso de trabajo (ThreadPool)

El grupo de subprocesos utilizado en .NET utiliza la clase ThreadPool. ThreadPool es una clase estática definida en el espacio de nombres System.Threading e introducida desde .NET 1.1.

Llamar al método QueueUserWorkItem puede colocar una operación asíncrona de cálculo limitado en la cola del grupo de subprocesos.Este método agrega un elemento de trabajo y datos de estado opcionales a la cola del grupo de subprocesos.
WorkItem: un método identificado por el parámetro callBack que es invocado por un subproceso de grupo de subprocesos. Se puede pasar un parámetro de estado al método (es necesario encapsular más de un parámetro como una clase de entidad).

1  public static bool QueueUserWorkItem(WaitCallback callBack);
2  public static bool QueueUserWorkItem(WaitCallback callBack, object state);
class userThreadPool
    {
    
    

        static void Main(string[] args)
        {
    
    
            //方式一
            {
    
    
                ThreadPool.QueueUserWorkItem(n => Test("Test-ok"));
            }
            //方式二
            {
    
    
                WaitCallback waitCallback = new WaitCallback(Test);
                ThreadPool.QueueUserWorkItem(n => waitCallback("WaitCallback"));//两者效果相同 ThreadPool.QueueUserWorkItem(waitCallback,"Test-ok");
            }
            //方式三
            {
    
    
                ParameterizedThreadStart parameterizedThreadStart = new ParameterizedThreadStart(Test);
                ThreadPool.QueueUserWorkItem(n => parameterizedThreadStart("ParameterizedThreadStart"));
            }
            //方式四
            {
    
    
                TimerCallback timerCallback = new TimerCallback(Test);
                ThreadPool.QueueUserWorkItem(n => timerCallback("TimerCallback"));
            }
            //方式五
            {
    
    
                Action<object> action = Test;
                ThreadPool.QueueUserWorkItem(n => Test("Action"));
            }
            //方式六
            ThreadPool.QueueUserWorkItem((o) =>
            {
    
    
                var msg = "lambda";
                Console.WriteLine("执行方法:{0}", msg);
            });


            Console.ReadKey();
        }
        static void Test(object o)
        {
    
    
            Console.WriteLine("执行方法:{0}", o);
        }

    }
}

inserte la descripción de la imagen aquí

Métodos comunes de grupo de subprocesos

Varios métodos comúnmente utilizados por ThreadPool son los siguientes:

método ilustrar
QueueUserWorkItem Iniciar un subproceso (subproceso de trabajo) en el grupo de subprocesos
Obtener subprocesos mínimos Recupera la cantidad mínima de subprocesos que el grupo de subprocesos puede crear a pedido en previsión de nuevas solicitudes.
Obtener subprocesos máximos El número máximo de subprocesos disponibles, todas las solicitudes superiores a este número permanecerán en cola hasta que se libere el subproceso del grupo de subprocesos.
Obtener subprocesos disponibles El número de subprocesos inactivos restantes.
Establecer subprocesos máximos Establezca el número máximo de subprocesos en el grupo de subprocesos (las solicitudes que excedan este valor ingresarán a la cola).
Establecer subprocesos mínimos Establezca el número mínimo de subprocesos que debe reservar el grupo de subprocesos.

Aviso:

1. Los subprocesos tienen sobrecarga de memoria, por lo que demasiados subprocesos en el grupo de subprocesos sin una utilización completa es una pérdida de memoria, por lo que es necesario limitar la cantidad mínima de subprocesos en el grupo de subprocesos.

2. La cantidad máxima de subprocesos en el grupo de subprocesos es la cantidad máxima de subprocesos que se pueden crear en el grupo de subprocesos. La situación real es que la cantidad de subprocesos en el grupo de subprocesos se crea a pedido.

línea de E/S

El subproceso I\O es un subproceso especialmente introducido por .NET para acceder a recursos externos. Para evitar que el subproceso principal se bloquee durante mucho tiempo al acceder a recursos externos, .NET ha establecido un método asíncrono para múltiples operaciones de E/S.

Por ejemplo:

  • FileStream: BeginRead, BeginWrite. Se iniciará una operación asincrónica cuando se llame a BeginRead/BeginWrite, pero la compatibilidad real con IOCP solo se puede obtener pasando el parámetro FileOptions.Asynchronous al crear un FileStream; de lo contrario, el método BeginXXX utilizará la implementación predeterminada definida en la clase base Stream. . El método BeginXXX en la clase base Stream usará el método BeginInvoke delegado para iniciar una llamada asincrónica; esto usará un subproceso adicional para ejecutar la tarea (no compatible con IOCP, lo que puede aumentar la pérdida de rendimiento).
  • DNS:BeginGetHostByName、BeginResolve。
  • Zócalo: BeginAccept, BeginConnect, BeginReceive, etc.
  • WebRequest:BeginGetRequestStream、BeginGetResponse。
  • Comando Sql: BeginExecuteReader, BeginExecuteNonQuery, etc. Esta es probablemente la operación asíncrona más utilizada al desarrollar una aplicación web. Si necesita compatibilidad con IOCP al realizar operaciones de base de datos, debe marcar Procesamiento asíncrono como verdadero (el valor predeterminado es falso) en la cadena de conexión
    ; de lo contrario, se generará una excepción al llamar a la operación BeginXXX.
  • WebServcie: por ejemplo,
    el método BeginXXX en Web Service Proxy generado por .NET 2.0 o WCF, y el método InvokeAsync de ClientBase en WCF.

Estos métodos asincrónicos se usan de manera similar, comenzando con Beginxxx (la implementación interna es ThreadPool.BindHandle) y terminando con Endxxx.

Aviso:

1. Para APM, se debe usar Endxxx para finalizar la asincronía; de lo contrario, se pueden producir fugas de recursos.

2. El método BeginInvoke delegado no puede obtener compatibilidad con IOCP.

3.IOCP no ocupa hilos.

El siguiente es un ejemplo del uso de WebRequest para llamar a una API asíncrona para ocupar un subproceso de E/S:

contexto de ejecución

Cada subproceso está asociado con una estructura de datos de contexto de ejecución, el contexto de ejecución (contexto de ejecución) incluye:

1. Configuración de seguridad (pila de compresión, atributo de subproceso principal, identidad de winodws).

2. Configuración del host (System.Threading.HostExecutionContextManager).

3. Datos de contexto de llamada lógica (métodos LogicalGetData y LogicalSetData de System.Runtime.Remoting.Messaging.CallContext).

Cuando un subproceso ejecuta su código, algunas operaciones se ven afectadas por las restricciones del contexto de ejecución del subproceso, especialmente la configuración de seguridad.

Cuando el subproceso principal usa un subproceso de trabajo para realizar tareas, el contexto de ejecución del primero se "vuela" (se copia) al subproceso de trabajo, lo que garantiza que cualquier operación realizada por el subproceso de trabajo use la misma configuración de seguridad y configuración de host.

De forma predeterminada, CLR hace que el contexto de ejecución del subproceso de inicialización "fluya" automáticamente a cualquier subproceso de trabajo. Pero esto tiene un impacto en el rendimiento. Lleva tiempo recopilar y copiar una gran cantidad de información contenida en las ejecuciones superior e inferior al subproceso auxiliar. Si el subproceso auxiliar utiliza más subprocesos auxiliares, se deben crear e inicializar más estructuras de datos de contexto de ejecución.

La clase ExecutionContext del espacio de nombres System.Threading, que permite controlar el flujo del contexto de ejecución de un subproceso:

 class 执行上下文
    {
    
    
        static void Main(string[] args)
        {
    
    
            //将一些数据放到主函数线程的逻辑调用上下文中
              CallContext.LogicalSetData("Action", "Jonins");
            //初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据
            ThreadPool.QueueUserWorkItem(state => Console.WriteLine("辅助线程A:" + System.Threading.Thread.CurrentThread.ManagedThreadId + ";Action={0}", CallContext.LogicalGetData("Action")));
            //现在阻止主线程执行上下文流动
            ExecutionContext.SuppressFlow();
            // 初始化要由另一个线程做的一些事情,线程池线程能访问逻辑上下文数据
            ThreadPool.QueueUserWorkItem(state => Console.WriteLine("辅助线程B:" + System.Threading.Thread.CurrentThread.ManagedThreadId + ";Action={0}", CallContext.LogicalGetData("Action")));
            //恢复主线程的执行上下文流动,以避免使用更多的线程池线程
            ExecutionContext.RestoreFlow();
            Console.ReadKey();
        }
    }

La clase ExecutionContext evita el flujo de contexto para mejorar el rendimiento del programa, lo que puede ser importante para las aplicaciones de servidor. Pero el rendimiento de la aplicación cliente no mejorará mucho. Además, debido a que el método SuppressFlow está marcado con el atributo [SecurityCritical], algunos clientes como Silverlight no pueden llamarlo.

Aviso:

1. Un subproceso secundario debe bloquear el flujo del contexto de ejecución cuando no necesita ni accede a la información de contexto.

2. El conocimiento sobre el flujo del contexto de ejecución también es útil cuando se utilizan objetos de tareas y se inician operaciones de E/S asíncronas.

Tres modos asincrónicos (alfabetización) y BackgroundWorker

1. APM y EAP y TAP

.NET admite tres modos de programación asíncrona: APM, EAP y TAP:

1. Patrón de diseño de programación asincrónica basada en eventos (EAP, Event-based Asynchronous Pattern)

La denominación en código del modo de programación EAP tiene las siguientes características:

1. Hay uno o más métodos llamados "[XXX]Async". Estos métodos pueden reflejar versiones sincronizadas que realizan las mismas operaciones en el subproceso actual.
2. Esta clase también puede tener un evento "[XXX]Completado" para monitorear el resultado del método asíncrono.
3. Puede tener un método "[XXX]AsyncCancel" (o simplemente CancelAsync) que cancela una operación asincrónica en curso.

2. Modelo de programación asíncrona (APM, Modelo de programación asíncrona)

El código de denominación del modo de programación de APM tiene las siguientes características:

1. La operación asíncrona que utiliza el patrón de diseño IAsyncResult se realiza a través de dos métodos denominados [BeginXXX] y [EndXXX], que inician y finalizan el nombre de la operación de operación asíncrona respectivamente. Por ejemplo, la clase FileStream proporciona métodos BeginRead y EndRead para leer bytes de un archivo de forma asíncrona.

2. Después de llamar a [BeginXXX], la aplicación puede continuar ejecutando instrucciones en el subproceso de llamada mientras se ejecutan operaciones asincrónicas en otro subproceso. Cada vez que se llama a [BeginXXX], la aplicación también debe llamar a [EndXXX] para obtener el resultado de la operación.

3. Modelo de programación basado en tareas (TAP, Task-based Asynchronous Pattern)

Basado en Task y Task del espacio de nombres System.Threading.Tasks, utilizado para representar operaciones asincrónicas arbitrarias. Discutir después de TAP. Haga clic aquí para obtener detalles sobre las tres operaciones asincrónicas: aquí

2.Trabajador de fondo

BackgroundWorker esencialmente usa subprocesos de trabajo en el grupo de subprocesos, pero esta clase es redundante (solo necesita comprenderla). Agregue un método personalizado al atributo DoWork de BackgroundWorker y agregue el método personalizado al subproceso agrupado a través de RunWorkerAsync para su procesamiento.

DoWork es esencialmente un evento (evento). El tipo de delegado está limitado a ningún valor de retorno y dos parámetros son de tipo Object y DoWorkEventArgs.

 public event DoWorkEventHandler DoWork;

 public delegate void DoWorkEventHandler(object sender, DoWorkEventArgs e);

epílogo

  • Los programadores usan más el grupo de subprocesos para usar los subprocesos de trabajo en el grupo de subprocesos para la codificación lógica.
  • En comparación con el subproceso de operación única (Subproceso), el conjunto de subprocesos (Subproceso) puede garantizar que la sobrecarga temporal de los trabajos de computación intensiva no cause una sobrecarga de la CPU (la cantidad de subprocesos activados es mayor que la cantidad de núcleos de CPU y el sistema debe realizar la programación de subprocesos de acuerdo con los intervalos de tiempo).
  • La sobrecarga afecta el rendimiento porque la división del tiempo requiere mucha sobrecarga de cambio de contexto e invalida las cachés de la CPU, que son necesarias para que los procesadores se programen de manera eficiente.
  • El CLR puede secuenciar tareas y controlar la cantidad de tareas iniciadas para evitar sobrecargar el grupo de subprocesos. CLR primero ejecuta tantas tareas simultáneas como núcleos de hardware y luego ajusta la cantidad de tareas simultáneas a través del algoritmo de escalada para garantizar que el programa cumpla con la curva de rendimiento óptima.

Supongo que te gusta

Origin blog.csdn.net/kalvin_y_liu/article/details/128241955
Recomendado
Clasificación