Bajar el ejemplo aquí.
En el tutorial previo, aprendiste que un proceso consta de al menos un hilo [thread]: el hilo primario. Un hilo es una cadena de ejecución. también puedes crear hilos adicionales en tu programa. Puedes concebir la programación multihilos [multithreading programming] como una programación multitareas [multitasking programming] dentro de un mismo programa. En términos de implementación, un hilo es una función que corre concurrentemente con el hilo principal. Puedes correr varias instancias de la misma función o puedes correr varias funciones simultáneamente dependiendo de tus requerimientos. La programación multihilos es específica de Win32, no existe una contraparte en Win16.
Los hilos corren en el mismo proceso, así que ellos pueden acceder a cualquiera de sus recursos tal como variables globales, manejadores etc. Sin embargo, cada hilo tiene su pila [stack] propia, así que las variables locales en cada hilo son privadas. Cada hilo también es propietario de su grupo de registros privados, así que cuando Windows conmuta a otros hilos, el hilo puede "recordar" su último estado y puede "resumir" la tarea cuando gana el control de nuevo. Esto es manejado internamente por Windows.
Podemos dividir los hilos en dos categorías:
Recomiendo la siguiente estrategia cuando se use la capacidad multihilo de Win32: Dejar que el hilo primario haga de interface de usuario y los otros hilos hagan el trabajo duro en el trasfondo. De esta manera, el hilo primario es como un Gobernador, los otros hilos son como el equipo del gobernador [Governor's staff]. El Gobernador delega tareas a su equipo mientras mantiene contacto con el público. El equipo del Gobernador ejecuta con obediencia el trabajo y lo reporta al Gobernador. Si el Gobernador fuera a realizar todas las tareas por sí mismo, el no podría atender bien al público ni a la prensa. Esto sería parecido a una ventana que está realizando una tarea extensa en su hilo primario: no responde al usuario hasta que la tarea ha sido completada. Este programa podría beneficiarse con la creación de un hilo adicional que sería el respondable de la extensa tarea, permitiendo al hilo primario responder a las órdenes del usuario.
Podemos crear un hilo llamando a la función CreateThread que tiene la siguiente sintaxis:
CreateThread
proto lpThreadAttributes:DWORD,\
dwStackSize:DWORD,\
lpStartAddress:DWORD,\
lpParameter:DWORD,\
dwCreationFlags:DWORD,\
lpThreadId:DWORD
La función
CreateThread se parece un poco a CreateProcess.
lpThreadAttributes
--> Puedes usar NULL si quieres que el hilo tenga el manejador de seguridad
por defecto.
dwStackSize
--> especifica el tamaño de la pila del
hilo. Si quieres que la pila del nuevo hilo tenga el mismo tamaño que
la pila del hilo primario, usa NULL en este parámetro.
lpStartAddress-->
Dirección de la función del hilo. Es la función que hará
el trabajo del hilo. Esta función DEBE recibir uno y sólo un parámetro
de 32-bits y regresar un valor de 32-bits.
lpParameter
--> El parámetro que quieres pasar a la función del hilo.
dwCreationFlags
--> 0 significa que el hilo corre inmediatamante después de que es creado.
Lo opuesto es la bandera CREATE_SUSPENDED.
lpThreadId
--> La función CreateThread llenará
el ID del hilo del nuevo hilo creado en esta dirección.
Si la llamada a CreateThread tiene éxito, regresa el manejador del hilo creado. Sino, regresa NULL.
La función del hilo corre tan pronto se realiza la llamada a CreateThread, a menos que especifiques la bandera CREATE_SUSPENDED en dwCreationFlags. En ese caso, el hilo es suspendido hasta que se llama a la función ResumeThread.
Cuando la función del hilo regresa con la instrucción ret, Windows llama a la función ExitThread para la función de hilo implícitamente. Tú mismo puedes llamar a la función ExitThread con tu función de hilo pero hay un pequeño punto qué considerar al hacer esto. Puedes regresar el código de salida del hilo llamando a la función GetExitCodeThread. Si quieres terminar un hilo desde otro, puedes llamar a la función TerminateThread. Pero sólo deberías usar esta función bajo circunstancias extremas ya que la función termina el hilo de inmediato sin darle chance al hilo de limpiarse después.
Ahora vamos a ver los métodos de comunicación entre los hilos.
Hay tres de ellos:
Los hilos comparten los recursos del proceso, incluyendo variables globales, así que los hilos pueden usar variables globales para comunicarse entre sí. Sin embargo este método debe ser usado con cuidado. La sincronización de hilos debe tenerse en cuenta. Por ejemplo, si dos hilos usan la misma estructura de 10 miembros, ¿qué ocurre cuando Windows de repente jala hacia sí el control de un hilo mientras éste estaba en medio de la actualización de la estructura? ¡El otro hilo quedará con datos inconsistentes en la estructura! No cometas ningún error, los programas multihilos son difíciles de depurar y de mantener. Este tipo de errores parecen ocurrir al azar lo cual es muy difícil de rastrear.
También puedes usar mensajes Windows para comunicar los hilos entre sí. Si todos los hilos son interface de usuario, no hay problema: este método puede ser usado como una comunicación en dos sentidos. Todo lo que tienes que hacer es definir uno o más mensajes de ventana hechos a la medida que sean significativos sólo para tus hilos. Defines un mensaje hecho a la medida usando el mensaje WM_USER como el valor base:
WM_MYCUSTOMMSG equ WM_USER+100h
Windows no usará ningún valor desde WM_USER en adelante para sus propios mensajes, así que puedes usar el valor WM_USER y superiores como tus valores para los mensajes hechos a la medida.
Sin uno de los hilos es una interface de ususario y el otro es un obrero, no puedes usar este método como dos vías de comunicación ya que el hilo obrero no tiene su propia ventana, así que no posee una cola de mensajes. Puedes usar el siguiente esquema:
Hilo
de interface de usuario ------> variable(s) global(es)----> Hilo obrero
Hilo
obrero ------> mensaje(s) de ventana hecho(s) a la medida----> Hilo de
interface de usuario
En realidad,
usaremos este método en nuestro ejemplo.
El último
método de comunicación es un objeto de evento. Puedes concebir
un objeto de evento como un tipo de bandera. Si el objeto evento es un estado
"no señalado" [unsignalled], el hilo está durmiendo
o es un durmiente, en este estado, el hilo no recibe una porción de tiempo
del CPU. Cuando el objeto de evento está en estado "señalado"
[signalled], Windows "despierta" el hilo e inicia la ejecución de la
tarea asignada.
Deberías bajar el archivo zip y correr thread1.exe. Haz click sobre el elemento de menú "Savage Calculation". Esto le ordenará al programa que ejecute "add eax,eax " por 600,000,000 veces. Nota que durante ese tiempo, no puedes hacer nada con la ventana principal: no puedes moverla, no puedes activar su menú, etc. Cuando el cálculo se ha completado, aparece una caja de mensaje. Después de eso, la ventana acepta tus órdenes normalmente.
Para evitar este tipo de inconvenientes al usuario, podemos mover la rutoina de "cálculo" a un hilo obrero separado y dejar que el hilo primario continúe con su tarea de interface de usuario. Incluso puedes ver que aunque la ventana principal responde más lento que de costumbre, todavía responde
.386
.model
flat,stdcall
option
casemap:none
WinMain
proto :DWORD,:DWORD,:DWORD,:DWORD
include
\masm32\include\windows.inc
include
\masm32\include\user32.inc
include
\masm32\include\kernel32.inc
includelib
\masm32\lib\user32.lib
includelib
\masm32\lib\kernel32.lib
.const
IDM_CREATE_THREAD
equ 1
IDM_EXIT
equ 2
WM_FINISH
equ WM_USER+100h
.data
ClassName
db "Win32ASMThreadClass",0
AppName
db "Win32 ASM MultiThreading Example",0
MenuName
db "FirstMenu",0
SuccessString
db "The calculation is completed!",0
.data?
hInstance
HINSTANCE ?
CommandLine
LPSTR ?
hwnd
HANDLE ?
ThreadID
DWORD ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain
proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain
endp
WndProc
proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
0,\
ADDR ThreadID
invoke CloseHandle,eax
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc
endp
ThreadProc
PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc
ENDP
end
start
.if ax==IDM_CREATE_THREAD
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
La función
de arriba crea un hilo que creará un procedimiento llamado ThreadProc
que correrá concurrentemente con el hilo primario. Después de
una llamada satisfactoria, CreateThread regresa de inmediato y ThreadProc comienza
a correr. Puesto que no usamos manejadores de hilos, deberíamos cerrarlo,
sino habrá cierta carencia de memoria. Nota que al cerrar el manejador
del hilo éste no termina. El único efecto es que ya no se puede
usar más el manejador del hilo.
ThreadProc
PROC USES ecx Param:DWORD
mov ecx,600000000
Loop1:
add eax,eax
dec ecx
jz Get_out
jmp Loop1
Get_out:
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
ret
ThreadProc
ENDP
Como puedes ver, ThreadProc ejecuta un cáculo salvaje que tarda un poco para terminar y cuando finaliza envía un mensaje WM_FINISH a la ventana principal. WM_FINISH es nuestro mensaje hecho a la medida definido como:
no tienes que agregar WM_USER con 100h pero es más seguro hacerlo así.
El mensaje WM_FINISH es significativo sólo dentro del programa. Cuando la ventana principal recibe el mensaje WM_FINISH, responde desplegando una caja de mensaje que dice que el cálculo ha terminado.
Puedes crear
varios hilos en sucesión enviando varias veces el mensaje "Create
Thread".
En este ejemplo,
la comuicación se realiza en un solo sentido ya que sólo un hilo
puede notificar la ventana principal. Si quieres que el hilo principal envíe
órdenes [commands] al hilo obrero, lo puedes hacer así:
[Iczelion's Win32 Assembly HomePage]
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por:
n u M I T_o r