7 SISTEMAS DE EJEMPLO.
7.2 Windows.
Windows trata la sincronización apoyado en el concepto de objeto.
Algunos objetos pueden estar en el estado de señalado o de no señalado. Un hilo puede esperar a que un objeto pase de no señalado a señalado, bloqueando su ejecución.
Los objetos implicados y la razón por la que pasan a estar señalados pueden ser:
En general, cuando un objeto pasa a estar señalado, se desbloquean todos los hilos que estuvieran esperando por él. Esto es cierto salvo en el caso de las exclusiones mutuas, en las que, como es lógico, sólo se desbloquea un hilo.
Para que un hilo de un proceso se bloquee esperando a que un objeto de Windows pase al estado de señalado, hay que usar la función del API
WaitForSingleObject
. El funcionamiento de esta
función no es difícil por lo que os remitimos al
resumen .
WaitForMultipleObjects
.
En este caso, podemos esperar hasta que todos pasen al estado
de señalado o hasta que al menos uno de ellos pase al estado
de señalado.
Esto lo controlará el parámetro de la función
fWaitAll
:
DWORD WaitForMultipleObjects( DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll, DWORD dwMilliseconds);El resto de los parámetros no presenta ninguna dificultad. Nos encontramos de nuevo un array que, como ya sabemos, necesita que pasemos en otro parámetro su longitud pues, en C, para la función la longitud del array sería desconocida si no lo hacemos.
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);Como tenemos por costumbre, dejaremos los atributos de seguridad a su valor por defecto (NULL). Debemos especificar en la llamada de creación el valor inicial del semáforo y su valor máximo (esto es diferente con respecto a UNIX).
CreateSemaphore
.
IPC_PRIVATE
. Esto se consigue
en Windows poniendo NULL
como último parámetro.
Si queremos compartir el semáforo sin nombre, debemos usar
la función del API que vimos en la sesión anterior
DuplicateHandle
.
OpenSemaphore
:
HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);En el primer parámetro especificaremos el tipo de acceso deseado. Normalmente será
SEMAPHORE_ALL_ACCESS
, aunque también
es posible obtener accesos parciales.
El segundo parámetro nos
servirá para especificar
si queremos que el handle sea heredable
por los procesos que creemos.
El último parámetro es el nombre del semáforo.
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);Especificaremos que queremos incrementar el semáforo en
lReleaseCount
unidades.
La función nos devolverá
en la dirección apuntada por lpPreviousCount
el
valor anterior del semáforo.
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds);El proceso que la invoque con el handle del semáforo efectuará una operación wait sobre él. No es posible decrementar atómicamente el semáforo en más de una unidad.
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);El valor
bInitialOwner
indicará si el hilo que
crea el mutex tendrá
inmediata posesión de él (y el mutex valor
nulo) o si, por el contrario, el mutex estará
libre (con valor unidad).
CreateEvent
:
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);Con esta función podemos indicar si queremos que el objeto nazca señalado o no (
bInitialState
).
Como todo buen objeto
de Windows, los hilos de los procesos se quedarán bloqueados
esperando sobre él hasta que pase
al estado de señalado.
Lo podemos poner en estado de señalado
con SetEvent
.
Lo podemos pasar a estado de no señalado
con ResetEvent
.
Podemos, incluso, invocar PulseEvent
y lograremos
que esté temporalmente en estado
de señalado para que se
desbloqueen todos los hilos
que estén esperando sobre él
y pasar a continuación al estado de no señalado.
CreateEvent
hacemos que
bManualReset
sea falso, cada vez que hagamos
SetEvent
no hará falta
que hagamos el correspondiente
ResetEvent
,
pues se hará de forma automática.
CRITICAL_SECTION
. Normalmente se usará
para delimitar
secciones críticas
dentro de un mismo proceso en las cuales sólo
puede haber un hilo ejecutando de por vez.
CRITICAL_SECTION
y darle valor inicial así:
CRITICAL_SECTION sc1; [...] InitializeCriticalSection(&sc1);Cada una de las partes del código que formen la sección crítica hay que rodearla por las instruciones:
EnterCriticalSection(&sc1); [[...Sección crítica...]] LeaveCriticalSection(&sc1);No hay que olvidarse de liberar la sección crítica cuando ya no se vaya a usar más con
DeleteCriticalSection
.
También existe
la posibilidad de probar a entrar en la sección
crítica pero no quedarse bloqueado
si no se puede (algo así
como la versión no bloqueante o IPC_NOWAIT
de
UNIX) con TryEnterCriticalSection
.
LONG
.LONG
. Las operaciones incluyen
el incremento, decremento e intercambio atómico:
LONG InterlockedIncrement( LPLONG lpAddend); LONG InterlockedDecrement( LPLONG lpAddend); LONG InterlockedExchange( LPLONG Target, LONG Value);Estas operaciones son importantes como vemos en teoría para poder dar soluciones de tipo hardware a la concurrencia. En este caso, además, tenemos mayor problema que con los procesos de UNIX pues la memoria es compartida por todos los hilos de ejecución de un proceso y el acceso compartido a las variables puede dar problemas. Estos problemas a veces permanecen ocultos hasta que usamos el programa en un ordenador multiprocesador, pues entonces aumenta la probabilidad de fallo.
DWORD SignalObjectAndWait( HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds, BOOL bAlertable);El significado de sus dos primeros parámetros es evidente; el tercero funciona igual que el correspondiente de
WaitForSingleObject
y el último, lo dejaremos
a FALSE
. Su significado excede el ámbito de lo razonable
para este curso. En cuanto a su valor de retorno también
es similar al de WaitForSingleObject
.
HILOsTRABAJADORes HILOGESTOR ================= ========== W(Mutex); idElegido=RealizarTareaYElecciOn(); while (elegido!=miId) W(Mutex); {SignalObjectAndWait(Mutex,Evento,INFINITE,FALSE); elegido=idElegido; W(Mutex);} PulseEvent(Evento); S(Mutex); S(Mutex); EsperaAHiloElegido();Los hilos que concurren a realizar la tarea, toman el mutex para poder observar el sistema tranquilamente. Viendo que no son el elegido, atómicamente, sueltan el mutex y se quedan esperando sobre un evento a que el hilo gestor realice otra elección. En ese momento, vuelve a tomar el mutex y lo reintenta hasta que tiene éxito;. El consumo de CPU que produce este esquema no es óptimo frente a usar un objeto de sincronización por cada hilo trabajador, pero es mínimo.
Esperando: Mu R2 Ascensor: Mo Escalera: R1 Esperando: Mu R1 R2 Ascensor: Mo Escalera: Esperando: Mu R1 R2 Ascensor: Escalera: Mo etc.Soluciones y comentarios .
BOOL PostThreadMessage( DWORD idThread, UINT Msg, WPARAM wParam, LPARAM lParam);En los parámetros se especificará el identificador del hilo receptor del mensaje, el tipo de mensaje, el parámetro de 16 bits y el parámetro de 32 bits. En el tipo de mensaje, podemos usar el
WM_USER
y siguientes pues están
reservados para aplicaciones de usuario. El envío que realiza
esta función
es un envío no bloqueante en el sentido visto
en teoría.
Existe otra función SendMessage
que
no veremos que realiza envíos bloqueantes
(la función no
retorna hasta que el destinatario haya recibido el mensaje).
PeekMessage(&mensaje, NULL, WM_USER, WM_USER, PM_NOREMOVE);antes de poder recibir ningún mensaje.
BOOL PeekMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg);En el primer parámetro se depositará la información del mensaje espiado, el segundo lo dejaremos a
NULL
.
En el tercero y el cuarto especificaremos qué rango de
tipo de mensajes queremos comprobar.
El último parámetro
servirá para indicar si queremos que el mensaje desaparezca
de la cola o no, después de espiado.
BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);igual que
PeekMessage
con la diferencia de que
el mensaje siempre desaparece y de que la función se queda
bloqueada si no hay mensajes de los tipos solicitados en la cola.
Podemos usar WaitMessage
para ver si hay mensajes de
cualquier tipo en la cola.