Fuente: MSDN Library
Traducción, formato y edición por Slasher Keeper 
Procesos y Subprocesos
[/b][/size]
Introducción Las aplicaciones basadas en Win32 consisten de uno o más procesos. Un proceso, en términos simples, es un programa ejecutable. Uno o más subprocesos se corren en el contexto del proceso. Un subproceso es la unidad básica para la cual el sistema operativo asigna tiempo del procesador. Un subproceso puede ejecutar cualquier parte del código de un proceso, incluyendo partes que actualmente están siendo ejecutadas por otro subproceso. Un fiber es una unidad de ejecución que debe ser manualmente fijada por la aplicación. Los fibers se corren en el contexto de los subprocesos que los crean.
Un objeto job permite a grupos de procesos ser manejados como una unidad. Los objetos job son objetos que pueden ser nombrados, asegurados y compartidos y que controlan los atributos de los procesos relacionados con ellos. Las operaciones realizadas en los objetos job afectan a todos los procesos asociados con el objeto job. Estos objetos sólo pueden ser manipulados en Windows NT.
Acerca de los Procesos y los Subprocesos Cada proceso proporciona los recursos necesarios para ejecutar un programa. Un proceso tiene un espacio de dirección virtual, código ejecutable, referencias a objetos, variables de entorno, una prioridad base, y un mínimo y un máximo de espacio de trabajo. El espacio de trabajo es la memoria física asignada por el sistema operativo para el proceso. El espacio de trabajo contiene el código y las páginas de datos recientemente usadas por el proceso. Cada proceso es empezado con un subproceso solo, a menudo llamado subproceso primario, pero puede crear subprocesos adicionales desde cualquiera de sus subprocesos.
Todos los subprocesos de un proceso comparten su espacio de dirección virtual y los recursos del sistema. Además, cada subproceso mantiene control de excepciones, una prioridad de programa, y una serie de estructuras que el sistema usará para guardar el contexto del subproceso hasta que sea fijado. El contexto del subproceso incluye la serie de subprocesos de los registros de la máquina, la pila del núcleo, un bloque de información de subproceso o Thread Information Block (TIB), y la pila del usuario en el espacio de direcciones del proceso del subproceso.
Windows NT, Windows 95, y Windows 98 soportan preemptive multitasking o multitarea preventiva, que es la habilidad del sistema operativo para fijar el tiempo de ejecución para procesos múltiples y subprocesos que periódicamente suspenden la ejecución del subproceso que se está ejecutando actualmente para cambiar a otro subproceso de mayor prioridad. Esto se usa para crear el efecto de la ejecución simultánea de varios subprocesos desde varios procesos.
Estos temas se discuten a continuación:
• Multitarea
• Programación
• Subprocesos Múltiples
• Procesos Hijos
• Espacio de Trabajo del Proceso (Working Set)
• Fibers
Multitarea Un sistema operativo multitarea divide el tiempo disponible del procesador entre los procesos y los subprocesos que lo necesitan. El sistema es diseñado para multitarea preventiva; este asigna una porción de tiempo del procesador para que se ejecute cada subproceso. El subproceso que se está ejecutando actualmente es suspendido cuando esta porción de tiempo pasa, permitiendo correr otro subproceso. Cuando el sistema cambia de un subproceso a otro, guarda el contexto del subproceso apropiado y restaura el contexto guardado del siguiente subproceso en la cola.
La longitud de la porción de tiempo depende del sistema operativo y del procesador. Los múltiples subprocesos parecen que se están ejecutando al mismo tiempo porque cada porción de tiempo es pequeña (aproximadamente 20 milisegundos). Este es actualmente el caso de los sistemas de multiprocesador, donde los subprocesos ejecutables son distribuidos entre los procesadores disponibles. Sin embargo, hay que tener cuidado cuando se están usando múltiples subprocesos en una aplicación, porque el rendimiento del sistema puede disminuir si hay demasiados subprocesos.
Ventajas de la multitarea Para el usuario, la ventaja de la multitarea es la habilidad para tener varias aplicaciones abiertas y trabajando al mismo tiempo. Por ejemplo, un usuario puede estar editando un archivo de texto mientras otra aplicación puede estar calculando una planilla de cálculo.
Para el diseñador de las aplicaciones, es la habilidad de crear aplicaciones que usen más que un proceso y crear procesos que usen más que un subproceso de ejecución. Por ejemplo, un proceso puede tener un subproceso de interfaz de usuario que maneje las interacciones con el usuario (entrada del teclado y del mouse), y subprocesos de ejecución que realizan otras tareas mientras el subproceso de interfaz de usuario espera por una entrada del usuario. Si se da a un subproceso de interfaz de usuario una prioridad superior, la aplicación será más sensible para el usuario, mientras los subprocesos de ejecución usan el procesador eficientemente durante los momentos que no hay ninguna entrada del usuario.
Cuando usar la multitarea Hay dos maneras para implementar la multitarea: como un proceso simple con múltiples subprocesos o como procesos múltiples, cada uno con uno o más subprocesos. Una aplicación puede meter cada subproceso que necesite espacio de dirección privada y recursos privados en su proceso, para protegerlo de los subprocesos de otros procesos.
Un proceso multitarea puede manejar tareas exclusivas mutuamente con los subprocesos, tal como proporcionar una interfaz de usuario y realizar cálculos en segundo plano. Crear un subproceso multitarea puede ser también una manera eficiente para estructurar un programa que realice varias tareas similares o iguales concurrentemente. Por ejemplo, un servidor de named pipes o conductos con nombre puede crear un subproceso para cada proceso cliente que se ate al conducto. Este subproceso maneja la comunicación entre el servidor y el cliente. Este proceso podría usar múltiples subprocesos para lograr las siguientes tareas:
• Manejar las entradas para múltiples ventanas.
• Manejar las entradas desde varios dispositivos de comunicación.
• Distinguir tareas de prioridad variante. Por ejemplo, un subproceso de
prioridad alta maneja tareas de tiempo crítico, y un subproceso de prioridad
baja realiza otras tareas.
• Permitir una interfaz de usuario para permanecer sensible, mientras se asigna
tiempo para tareas de fondo.
Es típicamente más eficiente para una aplicación implementar multitarea para crear un proceso de subprocesos múltiples o simples, que crear varios procesos, por las siguientes razones:
• El sistema puede realizar un cambio de contexto más rápidamente para subprocesos
que para procesos, porque un proceso está más arriba que un subproceso (el contexto
de un proceso en más grande que el contexto de un subproceso)
• Todos los subprocesos de un proceso comparten el mismo espacio de direcciones y
puede acceder a las variables globales de un proceso, las cuales pueden simplificar
la comunicación entre subprocesos.
• Todos los subprocesos de un proceso pueden compartir todos los recursos abiertos,
tal como archivos y conductos de comunicación.
La API de Win32 también proporciona métodos alternativos que pueden ser usados en lugar de la multitarea. El más significante de estos métodos las entradas y salidas asíncronas (I/O) y la habilidad para esperar por múltiples eventos.
Un subproceso simple puede iniciar varias demandas de I/O que pueden ejecutarse concurrentemente usando I/O asíncronas. Las llamadas a I/O asíncronas pueden ser realizadas sobre archivos, conductos, y dispositivos de comunicación en serie.
Un subproceso simple puede bloquear su ejecución mientras espera que ocurra alguno o todos los eventos. Esto es más eficiente que usar múltiples subprocesos, cada no esperando un evento simple, y más eficiente que usar un subproceso simple, que consume tiempo de procesador para estar continuamente verificando por que ocurra algún evento.
Consideraciones de la multitarea La multitarea tiene requisitos de recursos y conflictos potenciales para ser considerados cuando se diseña una aplicación. Los requisitos de recursos son los siguientes:
• El sistema consume memoria para la información contextual requerida
por los procesos y subprocesos. Por eso, el número de procesos y subprocesos
que pueden ser creados está limitado por la memoria disponible.
• Guardando la información de un gran número de subprocesos se consume
significantemente tiempo de procesador. Si hay demasiados subprocesos, la
mayoría de ellos no serán capaces de realizar un progreso significativo. Si
la mayoría de los subprocesos actuales están en un proceso, los subprocesos
en otro proceso son ejecutados con menor frecuencia.
Proporcionando acceso compartido a recursos puede crear conflictos. Para evitarlos, se debe sincronizar el acceso a los recursos compartidos. Esto es verdad para los recursos del sistema (tal como puertos de comunicación), recursos compartidos por múltiples procesos (tal como archivos abiertos), o los recursos de un simple proceso (tal como variables globales) accedidos por múltiples subprocesos. El fracaso para sincronizar el acceso propiamente (en el mismo o en diferentes procesos) puede llevar a problemas tal como bloqueos o condiciones de carrera. La API de Win32 proporciona una serie de objetos y funciones de sincronización que se pueden usar para coordinar recursos compartidos entre múltiples subprocesos.
Programación El programador del sistema controla la multitarea determinando cual de los procesos que están compitiendo recibe la siguiente porción de tiempo del procesador. El programador determina qué subproceso es el siguiente en ejecutarse usando su prioridad de programación.
A continuación se dicuten los siguientes temas:
• Prioridades de programación
• Cambios de contexto
• Aumentos de prioridad
• Inversión de prioridad
Prioridades de programación Cada uno de los subprocesos tiene asignada una prioridad de programación. El rango de los niveles de prioridad es desde cero (la menor prioridad) a 31 (la mayor prioridad). Sólo el subproceso de página cero puede tener una prioridad de cero. El subproceso de página cero es un subproceso del sistema.
La prioridad de cada subproceso está determinada por los siguientes criterios:
• La clase de prioridad de su proceso
• El nivel de prioridad del subproceso dentro de la clase de prioridad de su proceso.
La clase de prioridad y el nivel de prioridad son combinados para formar la prioridad base de un subproceso.
Clases de prioridad Cada proceso pertenece a una de las siguientes clases de prioridad:
IDLE_PRORITY_CLASS (Inactivo)
NORMAL_PRIORITY_CLASS (Normal)
HIGH_PRIORITY_CLASS (Mayor)
REALTIME_PRIORITY_CLASS (Modo Real)
Predeterminadamente, la clase de prioridad de un proceso es NORMAL_PRIORITY_CLASS. Hay que usar la función CreateProcess para especificar la clase de prioridad de un proceso hijo cuando se crea. Hay que usar la función SetPriorityClass para cambiar la clase de prioridad de un proceso y GetPriorityClass para determinar la clase de prioridad actual de un proceso.
Los procesos que monitorean el sistema, tales como protectores de pantalla o aplicaciones que periódicamente actualizan la pantalla deberían usar IDLE_PRIORITY_CLASS. Esto previene que los subprocesos de este proceso, los cuales no tienen prioridad alta, interfieran con los subprocesos de prioridad alta.
Hay que usar HIGH_PRIORITY_CLASS con cuidado. Si un subproceso se corre con el mayor nivel de prioridad por período extenso, otros procesos en el sistema no podrán obtener tiempo del procesador. Si varios procesos son establecidos a prioridad alta al mismo tiempo, los subprocesos pierden su efectividad. La clase de prioridad alta debe ser reservada para subprocesos que deben responder a eventos de tiempo crítico. Si la aplicación realiza una tarea que requiere la clase de prioridad alta mientras el resto de sus tareas son de prioridad normal, hay que usar SetPriorityClass para aumentar la clase de prioridad de la aplicación temporalmente; entonces reducirlo cuando la tarea haya sido completada. Otra estrategia es crear un proceso de prioridad alta que tiene todos sus subprocesos bloqueados la mayoría del tiempo, despertando a los subprocesos sólo cuando las tareas críticas son necesarias. El punto importante es que un subproceso de prioridad alta debería ser ejecutado por un tiempo breve, y sólo cuando deba realizar una tarea crítica.
Nunca se debería usar REALTIME_PRIORITY_CLASS, porque esto interrumpe a los subprocesos del sistema que manejan la entrada del mouse, la entrada del teclado, y las actualizaciones en segundo plano de los datos del disco. Esta clase puede ser apropiada para aplicaciones que le "hablen" directamente al hardware o que realicen breves tareas que deberían tener interrupciones limitadas.
Nivel de prioridad Los siguientes son los niveles de prioridad dentro de cada clase de prioridad:
THREAD_PRIORITY_IDLE (Inactivo)
THREAD_PRIORITY_LOWEST (Menor)
THREAD_PRIORITY_BELOW_NORMAL (Por debajo de lo normal)
THREAD_PRIORITY_NORMAL (Normal)
THREAD_PRIORITY_ABOVE_NORMAL (Por arriba de lo normal)
THREAD_PRIORITY_HIGHEST (Mayor)
THREAD_PRIORITY_TIME_CRITICAL
Todos los subprocesos son creados usando THREAD_PRIORITY_NORMAL. Esto significa que la prioridad del subproceso es la misma que la clase de prioridad del proceso. Luego de crear un subproceso, se usa la función SetThreadPriority para ajustar su prioridad relativa a otros subprocesos en el proceso.
Una estrategia típica es usar THREAD_PRIORITY_ABOVE_NORMAL o THREAD_PRIORITY_HIGHEST para el subproceso de entrada del proceso, para asegurar que la aplicación está respondiendo al usuario. Los subprocesos en segundo plano, particularmente aquellos que usan intensamente el procesador, pueden ser establecidos a THREAD_PRIORITY_BELOW_NORMAL o THREAD_PRIORITY_LOWEST, para asegurar que pueden ser apropiados cuando sea necesario. Sin embargo, si se tiene un subproceso esperando por otro subproceso que tiene una prioridad baja para completar alguna tarea, es seguro bloquear la ejecución del subproceso de mayor prioridad que está esperando. Para hacer esto, se usa alguna función de espera, sección crítica, o la función Sleep, SleepEx o SwitchToThread. Esto es preferible antes que tener a un subproceso ejecutando un bucle. Por otra parte, el proceso puede bloquearse, porque el subproceso con la prioridad más baja nunca es programado
Para determinar el nivel de prioridad actual de un subproceso, se usa la función GetThreadPriority.
Prioridad Base El nivel de prioridad de un subproceso está determinado por la clase de prioridad de su proceso y su nivel de prioridad. La clase de prioridad y el nivel de prioridad son combinados para formar la prioridad base de cada subproceso.
La siguiente tabla muestra los niveles de prioridad base por combinaciones de clases de prioridad y valores de prioridad:
------------------------------------------------------------------------------
Base Clase de Prioridad del Proceso Nivel de Prioridad del Subproceso
------------------------------------------------------------------------------
1 IDLE_PRIORITY_CLASS,
NORMAL_PRIORITY_CLASS o THREAD_PRIORITY_IDLE
HIGH_PRIORITY_CLASS
------------------------------------------------------------------------------
2 IDLE_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
------------------------------------------------------------------------------
3 IDLE_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
------------------------------------------------------------------------------
4 IDLE_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
------------------------------------------------------------------------------
5 Background NORMAL_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
IDLE_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
------------------------------------------------------------------------------
6 Background NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
IDLE_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
------------------------------------------------------------------------------
7 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
Background NORMAL_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
------------------------------------------------------------------------------
8 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
NORMAL_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
------------------------------------------------------------------------------
9 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
NORMAL_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
------------------------------------------------------------------------------
10 Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
------------------------------------------------------------------------------
11 HIGH_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
Foreground NORMAL_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
------------------------------------------------------------------------------
12 HIGH_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
------------------------------------------------------------------------------
13 HIGH_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
------------------------------------------------------------------------------
14 HIGH_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
------------------------------------------------------------------------------
15 IDLE_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
NORMAL_PRIORITY_CLASS o
HIGH_PRIORITY_CLASS
------------------------------------------------------------------------------
HIGH_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
------------------------------------------------------------------------------
16 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_IDLE
------------------------------------------------------------------------------
22 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_LOWEST
------------------------------------------------------------------------------
23 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL
------------------------------------------------------------------------------
24 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_NORMAL
------------------------------------------------------------------------------
25 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_ABOVE_NORMAL
------------------------------------------------------------------------------
26 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_HIGHEST
------------------------------------------------------------------------------
31 REALTIME_PRIORITY_CLASS THREAD_PRIORITY_TIME_CRITICAL
------------------------------------------------------------------------------
Cambios de Contexto El programador mantiene una cola de subprocesos ejecutables para cada nivel de prioridad. Estos son conocidos como subprocesos preparados. Cuando un procesador se vuelve disponible, el sistema realiza un cambio de contexto. Los pasos en un cambio de contexto son:
1. Guarda el contexto del subproceso que se está ejecutando.
2. Pone el subproceso que se está ejecutando en el final de la cola por su prioridad.
3. Busca la cola de mayor prioridad que contiene subprocesos preparados.
4. Quita el subproceso de la cabeza de la cola, carga su contexto, y lo ejecuta.
Las siguientes clases no son subprocesos preparados:
• Subprocesos creados con la opción CREATE_SUSPEND
• Subprocesos detenidos durante la ejecución con las funciones SuspendThread o SwitchToThread.
• Subprocesos esperando por un objeto de sincronización o una entrada.
Hasta que los subprocesos que están suspendidos o bloqueados se preparen para ejecutarse, el programador no asigna ninguna porción de tiempo del procesador para ellos, indiferente de su prioridad.
Las razones más comunes para un cambio de contexto son:
• La porción de tiempo asignada a ese subproceso ha pasado.
• Un subproceso con mayor prioridad se ha preparado para ser corrido.
• Un subproceso que se está ejecutando necesita esperar.
Aumentos de prioridad Cada subproceso tiene una prioridad dinámica. Esta es la prioridad que el programador usa para determinar cual subproceso ejecutar. Inicialmente, la prioridad dinámica de un subproceso es la misma que su prioridad base. El sistema puede aumentar y disminuir la prioridad dinámica, para asegurarse que es obediente y que otros subprocesos no están muy necesitados de tiempo del procesador. El sistema no puede aumentar la prioridad de subprocesos con un nivel de prioridad base entre 16 y 31. Sólo subprocesos con una prioridad base entre 0 y 15 reciben aumentos de la prioridad dinámica.
El sistema aumenta la prioridad dinámica de un subproceso para reforzar su sensibilidad como sigue:
• Cuando un proceso que usa NORMAL_PRIORITY_CLASS es traído al primer plano, el programador
aumenta la clase de prioridad del proceso asociado con la ventana de primer plano, entonces
es mayor o igual a la clase de prioridad de cualquier proceso en segundo plano. La clase de
prioridad vuelve a su estado original cuando el proceso no es más el del primer plano.
• Cuando la ventana recibe entradas, tal como mensajes de un temporizador (Timer), mensajes del
mouse, o entradas del teclado, el programador aumenta la prioridad del subproceso que tiene a la ventana.
• Cuando las condiciones de espera para un subproceso bloqueado son cumplidas, el programador aumenta
la prioridad del subproceso. Por ejemplo, cuando una operación de espera asociada con la finalización
de funciones de I/O en el disco o entradas del teclado, el subproceso recibe un aumento de prioridad.
Después de aumentar la prioridad dinámica de un subproceso, el programador reduce esa prioridad en un nivel cada vez que el subproceso completa su porción de tiempo, hasta que el subproceso vuelve a tener su prioridad base. La prioridad dinámica de un subproceso nunca es menor que su prioridad base.
Inversión de prioridad La inversión de prioridad ocurre cuando dos o más subprocesos con diferentes prioridades están en la contienda para ser programados. Por ejemplo un simple caso con tres subprocesos: thread1, thread2 y thread3. Thread1 es de prioridad alta y está preparado para ser programado. Thread2, un subproceso de prioridad baja, está ejecutando código en una sección crítica. Thread1, el subproceso de prioridad alta, comienza esperando por un recurso compartido de thread2. Thread3 tiene prioridad media. Thread3 recibe todo el tiempo del procesador, porque el subproceso de prioridad alta (thread1) está esperando por recursos compartidos del subproceso de prioridad baja (thread2). Thread2 no dejará la sección crítica, porque no tiene la mayor prioridad y no se programará.
Windows NT: El programador resuelve este problema elevando al azar la prioridad de los subprocesos preparados. Los subprocesos de prioridad baja son ejecutados por bastante tiempo para salir de la sección crítica, y el subproceso de prioridad alta puede entrar en la sección crítica. Si el subproceso de prioridad baja no tiene suficiente tiempo de la CPU para salir de la sección crítica la primera vez, obtendrá otra oportunidad durante la siguiente ronda del programador.
Windows 95: Si el subproceso de prioridad alta es dependiente de un subproceso de prioridad baja que no será asignado para correr porque un subproceso de prioridad media está obteniendo todo el tiempo de la CPU, el sistema reconoce que el proceso de prioridad alta es dependiente de un subproceso de prioridad baja. Será elevada la prioridad del subproceso de prioridad baja hasta el valor de la prioridad del subproceso de prioridad alta. Esto le permitirá al subproceso que anteriormente tenía la menor prioridad ejecutar y descargar el subproceso de prioridad alta que estaba esperándolo.
Subprocesos Múltiples Cada proceso es comenzado con un subproceso simple, pero puede crear más subprocesos desde cualquiera de sus subprocesos.
Esta sección discute los siguientes temas:
• Crear Subprocesos
• Tamaño de la Pila del Subproceso
• Referencias de Subprocesos e identificadores
• Suspender la Ejecución de un Subproceso
• Sincronizar la Ejecución de Subprocesos Múltiples
• Subprocesos Múltiples y Objetos GDI
• Almacén Local del Subproceso
• Crear Ventanas en Subprocesos
• Terminar un Subproceso
• Momentos del Subproceso
Crear Subprocesos La función CreateThread crea un nuevo subproceso para un proceso. El subproceso que se está creando debe especificar la dirección inicial del código que el nuevo subproceso va a ejecutar. Típicamente, la dirección inicial es el nombre de la función definido en el código del programa. Esta función toma un parámetro simple y devuelve un valor (DWORD para C++ y Long para Visual Basic). Un proceso puede contener múltiples subprocesos ejecutando la misma función simultáneamente.
El ejemplo siguiente muestra cómo crear un nuevo subproceso que ejecuta la función local definida como ThreadFunc. Para mayor comodidad voy a escribir el ejemplo en C++ y en Visual Basic.
#include "windows.h"
DWORD WINAPI ThreadFunc( LPVOID lpParam )
{
char szMsg[80];
wsprintf( szMsg, "ThreadFunc: Parameter = %d\n", *lpParam );
MessageBox( NULL, szMsg, "Thread created.", MB_OK );
return 0;
}
VOID main( VOID )
{
DWORD dwThreadId, dwThrdParam = 1;
HANDLE hThread;
hThread = CreateThread(
NULL, // sin atributos de seguridad
0, // usar el tamaño de pila prederminado
ThreadFunc, // la function del subproceso
&dwThrdParam, // argumento para la function del subproceso
0, // usar las opviones de creación predeterminadas
&dwThreadId); // devuelve el identificador del subproceso
// Verifica el valor devuelto para el éxito
if (hThread == NULL)
ErrorExit( "No se pudo crear el Subproceso" );
CloseHandle( hThread );
}
Este es el mismo código para Visual Basic:
Private Declare Function CreateThread Lib “kernel32” _
(ByVal lpThreadAttributes As Long, _
ByVal dwStackSize As Long, _
ByVal lpStartAddress As Long, _
ByVal lpParameter As Long, _
ByVal swCreationFlags As Long, _
lpThreadID As Long) As Long
Function ThreadFunc(ByVal lParam As Long) As Long
MsgBox “Thread Created” & vbCrLf & “lpParam: “& lParam, vbExclamation
ThreadFunc = True
End Function
Sub Main()
Dim hThread&, dwThreadID&, dwThrdParam&
dwThrdParam = 1
hThread = CreateThread(0&, 0&, AddressOf ThreadFunc, dwThrdParam&, 0&, dwThreadID&)
End Sub
Para simplificar, el ejemplo pasa un puntero a un valor DWORD como un argumento a la función del subproceso. Esto podría ser un puntero a cualquier tipo de datos o estructura, o podría omitirse totalmente pasando un puntero NULL y eliminando las referencias al parámetro en ThreadFunc.
Es arriesgado pasar la dirección de una variable local si el subproceso que lo está creando existe antes que el nuevo subproceso, porque el puntero se invalidará. En cambio, tampoco se puede pasar un puntero a memoria asignada dinámicamente o hacer que el subproceso que está creando espere que el nuevo subproceso termine. Los datos también pueden ser pasados desde el subproceso que está creando al nuevo subproceso usando variables globales. Con variables globales, es usualmente necesario sincronizar el acceso por múltiples subprocesos. Para más información ver Sincronizar la Ejecución de Subprocesos Múltiples más adelante en esta sección.
En procesos sonde los subprocesos pueden crear múltiples subprocesos para ejecutar el mismo código, es inconveniente usar variables globales. Por ejemplo, un proceso que habilita al usuario para abrir varios archivos al mismo tiempo, puede crear un nuevo subproceso para cada archivo, con cada uno de los subprocesos ejecutando el mismo código de la función del subproceso. El subproceso que está creando puede pasar información única (tal como el nombre del archivo) requerida por cada instancia de la función del subproceso como un argumento. No se puede usar una simple variable global para este propósito, pero se podría usar un búfer de cadena asignado dinámicamente.
El subproceso que está creando al nuevo puede usar los argumentos de CreateThread para especificar lo siguiente:
• Los atributos de seguridad para la referencia al nuevo subproceso. Estos atributos de seguridad incluye
una opción de herencia que determina si la referencia puede ser heredada por los procesos hijos. Los
atributos de seguridad también incluyen un descriptor de seguridad, al cual el sistema usa para realizar
verificaciones de acceso en todos los usos subsecuentes de la referencia al subproceso antes de que se
conceda el acceso.
• El tamaño inicial de la pila del nuevo subproceso. La pila del subproceso es asignada automáticamente
en el espacio de memoria del proceso; el sistema incrementa la pila como necesita y la libera cuando
el subproceso termina.
• Una opción de creación que te habilita para crear el subproceso en un estado de suspensión. Cuando
está suspendido un subproceso no se ejecuta hasta que la función ResumeThread es llamada.
También se puede crear un subproceso llamando a la función CreateRemoteThread. Esta función es usada por procesos debuggers (depuradores) para crear un subproceso que se ejecute en el espacio de direcciones en el proceso que está siendo depurado.
Tamaño de la Pila del Subproceso Cada nuevo subproceso recibe su propio espacio de pila, que consiste en memoria reservada y encargada. Prederminadamente, cada subproceso usa 1 MB de memoria reservada, y una página de memoria encargada. El sistema encargará un bloque de la memoria de la pila reservada como necesite, hasta que la pila no pueda crecer más. Para especificar un tamaño de pila diferente del predeterminado, se debe usar una opción de la línea de comandos que algún compilador traiga para especificar este tamaño.
Para incrementar el espacio de pila que es inicialmente encargada por un proceso, hay que especificar el valor en el parámetro dwStackSize de la función CreateThread. Este valor es redondeado a la página más cercana y usado para establecer el tamaño inicial de la memoria encargada. La llamada a CreateThread fallará si no hay suficiente memoria para encargar el número de bytes que se especificó. Si el valor de dwStackSize es menor que el tamaño predeterminado, el nuevo subproceso usa el mismo tamaño que el subproceso que lo ha creado.
La pila se libera cuando el subproceso termina.
Controladores de Subprocesos e identificadores Cuando un nuevo subproceso es creado por la función CreateThread o CreateRemoteThread, se devuelve el controlador de subproceso. Predeterminadamente, este controlador tiene todos los derechos de acceso, y –sujeto a la comprobación de acceso de seguridad- puede ser usada en cualquiera de las funciones que acepten un controlador de subproceso. Este controlador puede ser heredado por procesos hijos, dependiendo de la opción de herencia especificada cuando se crea. El controlador puede ser duplicado usando DuplicateHandle, la cual te habilita para crear un controlador de subproceso con un subconjunto de los derechos de acceso. El controlador es válido hasta que se cierre, incluso después de que el subproceso que lo representa ha terminado.
Las funciones CreateThread y CreateRemoteThread también devuelven un identificador que identifican únicamente al subproceso en todo el sistema. Un subproceso puede usar la función GetCurrentThreadId para obtener su propio identificador de subproceso. Los identificadores son válidos desde el momento que los subprocesos fueron creados hasta que el subproceso haya terminado.
La API de Win32 no proporciona ninguna manera de obtener el controlador de un subproceso desde el identificador de subproceso. Si los controladores fueran hechos disponibles de esta manera, el subproceso que los contiene podría fallar porque otro proceso inesperadamente realizó una operación en uno de sus subprocesos, tal como suspenderlo, hacer que continúe, ajustando su prioridad o terminándolo. En cambio, se debe pedir el controlador desde el subproceso creador o desde el mismo subproceso. Un subproceso puede usar GetCurrentThread para recuperar un pseudo-controlador de su mismo objeto de subproceso. Este pseudo controlador es válido sólo para el proceso que invocó la función; no puede ser heredado o duplicado para usar con otros procesos. Para obtener el controlador real de un subproceso, hay que pasar el pseudo-controlador a la función DuplicateHandle.
Suspender la Ejecución de un Subproceso Un subproceso puede suspender y continuar la ejecución de otro subproceso usando las funciones SuspendThread y ResumeThread. Mientras un subproceso está suspendido, no es programado y no se le asigna tiempo del procesador.
La función SuspendThread no es particularmente útil para la sincronización porque no puede controlar el punto en el código en que el subproceso ha suspendido la ejecución. Sin embargo, se puede querer suspender un subproceso en una situación en donde se está esperando una entrada de usuario que podría cancelar el trabajo que el subproceso está realizando. Si la entrada de usuario cancela el trabajo, tiene que salir del subproceso; de otra forma llamar a ResumeThread.
Si un subproceso es creado en estado de suspensión (con la opción CREATE_SUSPEND), no empieza a ejecutarse hasta que otro subproceso llame a ResumeThread con la referencia del subproceso suspendido. Esto puede ser útil para iniciar el estado del subproceso antes de que empiece a ejecutarse.
El subproceso puede rendir su ejecución temporalmente por un intervalo especificado llamando a las funciones Sep y SleepEx. Esto es útil particularmente en casos donde un subproceso responde a la interacción del usuario, porque puede retrasar la ejecución el suficiente tiempo para permitir a los usuario observar los resultados de sus acciones. Durante el intervalo de tiempo, al subproceso no le es programado tiempo de procesador.
Sincronizar la Ejecución de Múltiples Subprocesos Para evitar las condiciones de carrera y los bloqueos, es necesario sincronizar el acceso de varios subprocesos a recursos compartidos. La sincronización también es necesaria para asegurar que el código independiente es ejecutado en la secuencia apropiada.
La API de Win32 proporciona una serie de objetos cuyas referencias son usadas para sincronizar múltiples subprocesos. Estos objetos incluyen:
• Búferes de Entrada de Consola
• Eventos
• Mutexes
• Secciones Críticas
• Procesos
• Semáforos
• Subprocesos
• Temporizadores (Timers)
El estado de cada uno de estos objetos está o señalado (Signaled) o no señalado (Unsignaled). Cuando se especifica un controlador a cualquiera de estos objetos en la llamada a alguna de las funciones de espera, la ejecución del subproceso actual es bloqueada hasta que el estado del objeto especificado sea señalado.
Algunos de estos objetos son útiles para bloquear un subproceso mientras ocurren algunos eventos. Por ejemplo, una referencia a un búfer de entrada de consola es señalada cuando hay entradas no leídas, tal como pulsaciones de teclas o clic de los botones del mouse. Las referencias a los procesos y subprocesos son señaladas cuando el proceso o el subproceso terminan. Esto le permite a un proceso, por ejemplo, crear un proceso hijo y entonces bloquear su ejecución hasta que el nuevo proceso haya terminado.
Otros objetos son útiles protegiendo los recursos compartidos de accesos simultáneos. Por ejemplo, múltiples subprocesos pueden tener cada uno una referencia a un objeto mutex. Antes de acceder a un recurso compartido, los subprocesos deben llamar a una de las funciones de espera para esperar que el estado del mutex sea señalado. Cuando el mutex haya sido señalado, sólo uno de los subprocesos en espera es descargado para acceder al recurso. El estado del mutex es inmediatamente restablecido a “no señalado” así cualquier otro de los subprocesos que están esperando permanecerán bloqueados. Cuando el subproceso hay terminado con el recurso, debe establecer el estado del mutex a señalado para permitirle a los otros subprocesos acceder al recurso.
Para los subprocesos de un proceso simple, los objetos sección-crítica proporcionan un medio más eficiente de sincronización que los objetos mutex. Una sección crítica es usada como un mutex para habilitar a un subproceso a la vez para usar un recurso protegido. Un subproceso puede usar la función EnterCriticalSection para apropiarse de una sección crítica. Si ya fue obtenida por otro subproceso, el subproceso que la pide es bloqueado. Un subproceso puede usar la función TryEnterCriticalSection para apropiarse de una sección crítica, sin bloquearse en el fracaso de obtener la sección crítica. Después de que recibe la propiedad, el subproceso es liberado para usar el recurso protegido. La ejecución de los otros subprocesos del proceso no es afectada a menos que intenten entrar en la misma sección crítica.
La función WaitForInputIdle hace que un subproceso espere hasta que un proceso especificado esté inicializado y esperando por una entrada de usuario sin una entrada pendiente. Llamar a WaitForInputIdle puede ser útil para sincronizar procesos padres e hijos, porque CreateProcess vuelve sin esperar que el proceso hijo termine su inicialización.
Para más información ver Sincronización más adelante en este artículo.
Subprocesos Múltiples y Objetos GDI Para aumentar el desempeño, el acceso a objetos de la interfase de dispositivos gráficos (GDI) (tal como paletas, contextos de dispositivos, regiones, y lo demás) no es serializada. Esto crea un peligro potencial para procesos que tienen múltiples subprocesos compartiendo estos objetos. Por ejemplo, si un subproceso elimina un objeto GDI mientras otro subproceso lo está usando, los resultados son imprevisibles. Este peligro puede ser evitado simplemente no compartiendo los objetos GDI. Si compartir es inevitable (o deseado), la aplicación debe proporcionar sus propios mecanismos para sincronizar el acceso.
Almacén Local del Subproceso (TLS) Todos los subprocesos de un proceso comparten el espacio de direcciones virtual y las variables globales de ese proceso. Las variables locales de la función del subproceso son locales para cada subproceso que corra la función. Sin embargo, las variables globales o estáticas son usadas por esa función tienen el mismo valor para todos los subprocesos. Con el almacén local del subproceso (TLS) se puede crear una única copia de una variable para cada subproceso. Usando TLS, un subproceso asigna un índice que puede ser usado por cualquier subproceso del proceso para devolver su única copia.
Hay que realizar los siguientes pasos para implementar TLS:
1. Usar la función TlsAlloc durante la inicialización del proceso o la biblioteca de vínculos
dinámicos (DLL) para asignar un índice TLS.
2. Para cada subproceso que necesite usar un índice TLS, asignar almacenamiento dinámico, entonces
usar la función TlsSetValue para asociar el índice con un puntero a un almacenamiento dinámico
3. Cuando un subproceso necesita acceder a su almacén, especifica el índice TLS en la llamada a
la función TlsGetValue para recuperar el puntero.
4. Cuando cada subproceso no necesite más el almacenamiento dinámico que ha asociado con el índice
TLS, debe liberar el índice. Cuando todos los subprocesos han terminado de usar un índice TLS,
se usa la función TlsFree para liberar el índice.
La constante TLS_MINIMUN_AVAILABLE define el número mínimo de índices TLS disponibles en cada proceso. Este mínimo se garantiza ser por lo menos 64 para todos los sistemas.
Es ideal usar TLS en una DLL. Realizar las operaciones TLS iniciales en la función DllMain en el contexto del proceso o el subproceso atado a la DLL. Cuando un nuevo proceso se ata a la DLL, se llama a la función TlsAlloc en la función del punto de entrada para asignar un índice TLS para ese proceso. Entonces se almacena el índice TLS en una variable global que es privada para cada proceso atado. Cuando un nuevo subproceso se ata a la DLL, se asigna memoria dinámica para ese subproceso en la función del punto de entrada, y se usa la función TlsSetValue con el índice TLS desde TlsAlloc para guardar datos privados para el índice. Entonces se puede usar el índice TLS en una llamada a la función TlsGetValue para acceder a los datos privados del subproceso que llama a la función desde dentro de cualquier función en la DLL. Cuando un proceso se desata de la DLL (libera la referencia) se llama a TlsFree.