7 SISTEMAS DE EJEMPLO.
7.2 Windows NT.
Windows NT 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 NT 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 remito 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 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 NT 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 NT, 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 vimos 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.
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.