Existen dos conceptos esenciales para crear programas concurrentes: sincronización y exclusión mutua. Veremos cómo estos dos tipos de bloqueos (los semáforos son más generalmente un tipo de mecanismo de bloqueo) nos ayudan a lograr la sincronización y la exclusión mutua.
Un semáforo tiene dos partes: un contador y una lista de tareas esperando para acceder a un recurso en particular. Un semáforo realiza dos operaciones: esperar (P) [esto es como adquirir un bloqueo] y liberar (V) [similar a liberar un bloqueo]: estas son las dos únicas operaciones que se pueden realizar en un semáforo. En un semáforo binario, el contador lógicamente va entre 0 y 1. Puede pensar que es similar a un bloqueo con dos valores: abierto/cerrado. Un semáforo de conteo tiene múltiples valores para contar.
Lo que es importante comprender es que el contador de semáforos realiza un seguimiento del número de tareas que no tienen que bloquear, es decir, pueden progresar. Las tareas se bloquean y se agregan a la lista del semáforo solo cuando el contador es cero. Por lo tanto, una tarea se agrega a la lista en la rutina P() si no puede avanzar, y "libera" usando la rutina V().
Ahora, es bastante obvio ver cómo los semáforos binarios se pueden utilizar para resolver la sincronización y la exclusión mutua, son esencialmente bloqueos.
ex. Sincronización:
thread A{
semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
A(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
;// some block of code B2
...
}
//thread B{
semaphore &s;
B(semaphore &s): s(s){} //constructor
foo(){
...
...
// some block of code B1
s.V();
..
}
main(){
semaphore s(0); // we start the semaphore at 0 (closed)
A a(s);
B b(s);
}
En el ejemplo anterior, B2 sólo puede ejecutar después de B1 ha terminado la ejecución. Digamos que el subproceso A se ejecuta primero - llega a sem.P() y espera, ya que el contador es 0 (cerrado). El hilo B aparece, termina B1 y luego libera el hilo A, que luego completa B2. Entonces logramos la sincronización.
Ahora veamos la exclusión mutua con un semáforo binario:
thread mutual_ex{
semaphore &s;
mutual_ex(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
//critical section
s.V();
...
...
s.P();
//critical section
s.V();
...
}
main(){
semaphore s(1);
mutual_ex m1(s);
mutual_ex m2(s);
}
La exclusión mutua es bastante simple, así - M1 y M2 no puede entrar en la sección crítica al mismo tiempo. Entonces, cada subproceso usa el mismo semáforo para proporcionar exclusión mutua para sus dos secciones críticas. Ahora, ¿es posible tener una mayor concurrencia? Depende de las secciones críticas. (Piensa en qué otra manera se podría usar semáforos para lograr la exclusión mutua .. pista: ¿Debo necesariamente sólo tendrá que utilizar un semáforo?)
Conteo de semáforos: Un semáforo con más de un valor. Veamos lo que esto implica: ¿un bloqueo con más de un valor? Tan abierto, cerrado, y ... hmm. ¿De qué sirve un bloqueo de etapas múltiples en exclusión o sincronización mutua?
Tomemos el más fácil de los dos:
Sincronización mediante un semáforo contador: Digamos que usted tiene 3 tareas - # 1 y 2 que desea ejecutar después 3. ¿Cómo diseñar su sincronización?
thread t1{
...
s.P();
//block of code B1
thread t2{
...
s.P();
//block of code B2
thread t3{
...
//block of code B3
s.V();
s.V();
}
Así que si su semáforo comienza cerrado, se asegura que el bloque t1 y t2, se añaden a la lista del semáforo. Luego viene todo t3 importante, finaliza su negocio y libera t1 y t2. ¿En qué orden se liberan? Depende de la implementación de la lista del semáforo. Podría ser FIFO, podría basarse en alguna prioridad particular, etc. (Nota: pensar en lo que hiciera distribuir V de su P y; s si quería t1 y t2 a ser ejecutadas en un orden determinado, y si no estaban al tanto de la aplicación del semáforo)
(Estudia:? ¿Qué pasa si el número de V es mayor que el número de P)
Exclusión mutua el uso de semáforos de conteo: me gustaría que para construir su propio pseudocódigo para esto (te hace entender las cosas mejor!), pero el concepto fundamental es el siguiente: un semáforo de conteo de contador = N permite que N tareas entren libremente en la sección crítica. Lo que esto significa es que tienes N tareas (o subprocesos, si quieres) ingresas a la sección crítica, pero la N + 1ª tarea se bloquea (va en nuestra lista de tareas bloqueadas favoritas) y solo se deja pasar cuando alguien V es el semáforo al menos una vez. Entonces, el contador de semáforos, en lugar de oscilar entre 0 y 1, ahora va entre 0 y N, permitiendo que N tareas entren y salgan libremente, ¡bloqueando a nadie!
Ahora, ¿por qué necesitarías un bloqueo tan extraño?¿No es el objetivo de la exclusión mutua no permitir que más de un chico acceda a un recurso? Piensa. (Sugerencia ... No siempre es suficiente una unidad en el ordenador, qué ...?)
Para pensar: es la exclusión mutua consigue teniendo un conteo de semáforos solo? ¿Qué sucede si tiene 10 instancias de un recurso y entran 10 hilos (a través del semáforo de conteo) y trata de usar la primera instancia?
Un buen caso de uso para un semáforo de conteo es si desea continuar solo después de que se hayan completado varios eventos diferentes. Por ejemplo, su programa se inicia e inicia 5 lecturas de base de datos asincrónicas diferentes, y no debería continuar hasta que se completen las 5. Debería incrementar el semáforo al comienzo de cada lectura e inmediatamente bloquear en 'semáforo == 0'. Al finalizar cada lectura, disminuya el semáforo. Su inicio continuará al finalizar la carga de datos. –