Vocabulario de subprocesos múltiples para subprocesos múltiples de Win32
Su aplicación se ha utilizado durante mucho tiempo: funciona muy bien, es confiable y todo, pero es muy lenta y no tiene idea de cómo aprovechar la idea de subprocesos múltiples. Sin embargo, espera un momento antes de empezar a hacer esto, porque hay muchas trampas que te llevan a creer que cierto diseño multihilo es perfecto cuando en realidad no lo es.
Antes de sacar conclusiones precipitadas sobre en qué entrar, primero aclaremos lo que no discutiremos en este artículo:
¿En la interfaz de programación de aplicaciones (API) de Microsoft Win32? Las bibliotecas que proporcionan acceso multiproceso son diferentes, pero este tema no nos preocupa. El conjunto de programas de muestra, Threadlib.exe, está escrito usando la API multiproceso Win32 en una aplicación Microsoft Foundation Class Library (MFC), pero ya sea que use la biblioteca Microsoft C Runtime (CRT), la biblioteca MFC o simplemente (básica) Win32 API para crear y mantener hilos, no nos importa.
De hecho, cada biblioteca finalmente debe llamar al servicio del sistema Win32 CreateThread para crear un subproceso de trabajo, y el subproceso múltiple en sí siempre debe ejecutarse a través del sistema operativo. El mecanismo de empaquetado que desee utilizar no afectará el tema de este artículo. Por supuesto, si usa una u otra biblioteca contenedora puede causar diferencias en el rendimiento, pero aquí analizamos principalmente la naturaleza del subproceso múltiple, no su contenedor.
Este artículo analiza las aplicaciones multiproceso que se ejecutan en una máquina con un solo procesador. Las computadoras multiprocesador son un tema completamente diferente y casi ninguna de las conclusiones analizadas en este artículo se aplica a las máquinas multiprocesador. No he tenido la oportunidad de ejecutar este ejemplo en una máquina escalable de subprocesos múltiples simétricos (SMP) que ejecute Windows NT. Si tienes la oportunidad, me encantaría saber tus resultados.
En este artículo, prefiero referirme a "informática" en general. Un cálculo se define como una subtarea de su aplicación que se puede realizar en su totalidad o en parte, antes o después de otro cálculo, o al mismo tiempo que otros cálculos. Por ejemplo, supongamos que una aplicación requiere datos de usuario y necesita guardarlos en el disco. Podemos suponer que los datos de entrada contienen un tipo de cálculo y que guardar los datos es otro tipo de cálculo. Según el diseño de cálculo de la aplicación, son posibles las dos situaciones siguientes: una es que el almacenamiento de datos y la entrada de nuevos datos se realicen al mismo tiempo y la otra es que los datos no se puedan introducir hasta que el usuario lo haya hecho; ingresó todos los datos. El primer caso generalmente se puede implementar utilizando alguna forma de subprocesos múltiples; a esta forma de organizar los cálculos la llamamos concurrencia o interacción. El último caso generalmente se puede lograr con una aplicación de un solo subproceso y en este artículo se denomina ejecución en serie.
El diseño de aplicaciones concurrentes es un proceso muy complejo. Las personas que ganan mucho dinero generalmente lo prefieren, porque generalmente se necesitan años de investigación para determinar cuánto beneficio hay de la ejecución simultánea de una tarea determinada. Este artículo no pretende enseñarle cómo diseñar aplicaciones multiproceso. En lugar de eso, le señalaré algunos de los problemas con el diseño de aplicaciones multiproceso y utilizaré pruebas de rendimiento de la vida real para analizar mis ejemplos. Después de leer este artículo, debería poder observar un diseño determinado y decidir si mejora el rendimiento general de la aplicación.
Parte de los pasos de diseño de una aplicación multiproceso es determinar dónde existen conflictos de acceso a datos multiproceso que podrían causar daños a los datos y cómo utilizar la sincronización de subprocesos para evitar dichos conflictos. Esta tarea (a la que en este artículo nos referiremos como serialización de subprocesos de ahora en adelante) es el tema de muchos artículos sobre subprocesos múltiples (por ejemplo, Sincronización sobre la marcha u Objetos de sincronización Win32 compuestos en la biblioteca de MSDN), y en este artículo no se analiza al respecto. estará involucrado en lo más mínimo. A los efectos de este artículo, asumiremos que los cálculos que requieren concurrencia no necesariamente comparten ningún dato y, por lo tanto, no requieren ningún subprocesamiento. Esta convención puede parecer un poco dura, pero tenga en cuenta que no existe una discusión "universal" sobre aplicaciones multiproceso sincronizadas, porque cada secuencia impondrá una estructura única de "espera-despertar" (patrón de espera y activación). subprocesos secuenciados, lo que afectará directamente el rendimiento.
La mayoría de las operaciones de entrada/salida (E/S) en Win32 se presentan en dos formas: sincrónicas o asincrónicas. Se ha demostrado que, en muchos casos, un diseño de subprocesos múltiples que utiliza E/S síncronas puede simularse mediante un diseño que utiliza E/S asincrónicas de un solo subproceso. Este artículo no analiza la E/S asincrónica de un solo subproceso como alternativa al multiproceso, pero le recomiendo que considere ambos diseños.
Tenga en cuenta que la forma en que está diseñado el sistema de E/S Win32 es proporcionar mecanismos que hagan que la E/S asíncrona sea superior a la E/S síncrona (por ejemplo, puertos de finalización de E/S). Planeo discutir la E/S síncrona y asíncrona en artículos futuros.
Como se señala en el artículo Múltiples subprocesos en la interfaz de usuario, los subprocesos múltiples y las interfaces gráficas de usuario (GUI) no funcionan bien juntos. En este artículo, asumo que los subprocesos en segundo plano pueden realizar su trabajo sin utilizar la GUI de Windows en absoluto; el tipo de subproceso con el que estoy tratando es simplemente un "subproceso de trabajo" que simplemente realiza cálculos en segundo plano sin requerir interacción directa con el Interacción.
Existe el cálculo finito, y también existe el correspondiente cálculo infinito. Un ejemplo de computación infinita es un hilo de "escucha" en una aplicación del lado del servidor que no tiene otro propósito que esperar a que un cliente se conecte al servidor. Después de que un cliente se ha conectado, el hilo envía una notificación al hilo principal y vuelve al estado de "escucha" hasta que se conecta el siguiente cliente. Naturalmente, dicho cálculo no puede residir en el mismo subproceso que la interfaz de usuario (UI) de la aplicación a menos que se utilice una operación de E/S asíncrona. (Tenga en cuenta que este problema en particular puede y debe resolverse mediante el uso de E/S asíncronas y puertos de finalización, en lugar de utilizar subprocesos múltiples, y estoy usando este ejemplo aquí solo con fines de demostración). En este artículo, solo consideraré el cálculo finito, es decir, las subtareas de una aplicación finalizarán después de un período de tiempo finito.