(Notas de estudio - Gestión de procesos) Cómo resolver conflictos de subprocesos múltiples

Para los recursos compartidos, si no hay bloqueo, en un entorno de subprocesos múltiples, es muy probable que se anule.


competencia y cooperación

En un sistema de CPU de un solo núcleo, para lograr la ilusión de que varios programas se ejecutan al mismo tiempo, el sistema operativo suele utilizar la programación de intervalos de tiempo para permitir que cada proceso ejecute un intervalo de tiempo a la vez. arriba, el siguiente proceso se cambia para ejecutarse.Dado que el tiempo de este intervalo de tiempo es muy corto, se produce un de concurrenciafenómeno

 Además, el sistema operativo también crea la ilusión de una enorme memoria virtual privada para cada proceso. Esta abstracción del espacio de direcciones hace que cada programa parezca tener su propia memoria, pero en realidad el sistema operativo secretamente crea múltiples espacios de direcciones. memoria o disco.

Si un programa tiene solo un flujo de ejecución, también significa que es de un solo subproceso. Por supuesto, un programa puede tener múltiples procesos de ejecución, que es el llamado programa de subprocesos múltiples. El subproceso es la unidad básica de programación y el proceso es la unidad básica de asignación de recursos.

Por lo tanto, los subprocesos pueden compartir recursos de proceso, como segmentos de código, espacio de almacenamiento dinámico, segmentos de datos, archivos abiertos y otros recursos, pero cada subproceso tiene su propio espacio de pila independiente.

 Luego viene el problema, si varios hilos compiten por los recursos compartidos, si no se toman medidas efectivas, se generará confusión en los datos compartidos.

Experimento: cree dos subprocesos y, respectivamente, se ejecutan 10 000 veces incrementando la variable compartida i en 1 , como se muestra en el siguiente código:

 Lógicamente hablando, la variable i debería ser 20000 al final , sin embargo, el resultado real es el siguiente:

 Después de ejecutarlo dos veces, se encuentra que el valor de i puede ser 20000 u otros resultados.

Cada ejecución no solo produce errores, sino que también da resultados diferentes. No se puede tolerar en la computadora, aunque es un error con una pequeña probabilidad, definitivamente sucederá con una pequeña probabilidad.

¿Por qué pasó esto?

Para entender por qué sucede esto, es necesario entender la secuencia de código que genera el compilador para actualizar la variable contador i, es decir, el orden en que se ejecutan las instrucciones ensambladoras.

En este ejemplo, solo queremos agregar el número 1 a i, por lo que el proceso de ejecución de su instrucción de ensamblaje correspondiente es el siguiente:

 Se puede encontrar que simplemente agregar el número 1 en realidad ejecutará una instrucción cuando la CPU esté funcionando . i  3 

Suponga que el subproceso 1 ingresa a esta área de código, carga el valor de i (suponiendo que es 50 en este momento) de la memoria en su registro, y luego agrega 1 al registro, y el valor de i en el registro es 51 en este momento. tiempo.

Ahora, sucede algo desafortunado: se produce una interrupción del reloj . Por lo tanto, el sistema operativo guarda el estado del subproceso que se está ejecutando actualmente en el bloque de control TCB del subproceso.

Ahora sucede algo aún peor: el subproceso 2 está programado para ejecutarse e ingresa el mismo código. También ejecuta la primera instrucción, obteniendo el valor de i de la memoria y colocándolo en un registro. En este momento, el valor de i en la memoria sigue siendo 50, por lo que el valor de i en el registro del subproceso 2 también es 50. Suponiendo que el subproceso 2 ejecuta las siguientes dos instrucciones, el valor de i en el registro es +1, y luego el valor de i en el registro se guarda en la memoria, por lo que el valor de la variable global i es 51 en este momento.

Finalmente, se produce otro cambio de contexto y el subproceso 1 reanuda la ejecución. Recuerde que ha ejecutado dos instrucciones de ensamblaje y ahora está listo para ejecutar la siguiente. Anteriormente, el valor de i en el registro del subproceso 1 era 51, por lo que después de ejecutar la última instrucción y guardar el valor en la memoria, el valor de la variable global i vuelve a establecerse en 51.

En términos simples, el código que aumenta i (valor 50) se ejecuta dos veces, lógicamente, el valor final de i debería ser 52, pero debido a una programación incontrolable , el valor final de i es 51.

Para el proceso de ejecución del hilo 1 y el hilo 2 por encima del 2, se puede expresar como:

 mutuamente excluyentes

La situación que se muestra arriba se denomina condición de carrera , cuando varios subprocesos compiten entre sí para manipular una variable compartida, debido a la mala suerte, es decir, se produce un cambio de contexto durante la ejecución, obtenemos resultados incorrectos, de hecho, cada ejecución puede obtener resultados diferentes. , por lo que existe incertidumbre en los resultados de salida .

Debido a que la ejecución de subprocesos múltiples de este código que opera variables compartidas puede conducir a una condición de carrera, llamamos a este código una sección crítica, que es un fragmento de código que accede a los recursos compartidos y no debe ser ejecutado por varios subprocesos al mismo tiempo .

Esperamos que este código sea mutuamente excluyente, es decir, cuando se ejecuta un hilo en la sección crítica, se debe evitar que otros hilos entren en la sección crítica , es decir, solo puede aparecer un hilo como máximo durante la ejecución de este. código.                                                                 

 Además, la exclusión mutua no es solo para subprocesos múltiples. Cuando varios subprocesos compiten por recursos compartidos, también se puede utilizar la exclusión mutua para evitar la confusión de recursos provocada por la competencia de recursos.

Sincronizar

La exclusión mutua resuelve el problema del uso de secciones críticas por procesos/subprocesos concurrentes. Esta interacción basada en el control de la sección crítica es relativamente simple. Siempre que un proceso/subproceso se una a la sección crítica, otros procesos/subprocesos que intenten ingresar a la sección crítica se bloquearán hasta que el primer proceso/subproceso abandone la sección crítica.

En los subprocesos múltiples, cada subproceso no se ejecuta necesariamente de forma secuencial, básicamente avanzan a una velocidad independiente e impredecible, pero a veces esperamos que varios subprocesos puedan cooperar estrechamente para lograr un objetivo común.

Por ejemplo, el subproceso 1 es responsable de leer los datos, mientras que el subproceso 2 es responsable de procesar los datos.Estos dos subprocesos cooperan y dependen el uno del otro. Cuando el subproceso 2 no recibe la notificación de activación del subproceso 1, siempre se bloqueará y esperará. Cuando el subproceso 1 termine de leer los datos y necesite pasar los datos al subproceso 2, el subproceso 1 despertará al subproceso 2 y entregará el datos al subproceso 2 para su procesamiento.

La llamada sincronización significa que los procesos/subprocesos simultáneos pueden necesitar esperar el uno al otro y comunicarse entre sí en algunos puntos clave.Este tipo de restricción mutua de esperar y comunicar información se denomina sincronización de proceso/subproceso .

PD: La sincronización y la exclusión mutua son dos conceptos diferentes:

  • Sincronización: [la operación A debe realizarse antes que la operación B], [la operación C debe realizarse después de que se completen tanto la operación A como la operación B], etc.
  • Exclusión mutua: [la operación A y la operación B no se pueden ejecutar al mismo tiempo];

Implementación y uso de exclusión mutua y sincronización

En el proceso de ejecución concurrente de procesos/subprocesos, existe una relación cooperativa entre procesos/subprocesos, como exclusión mutua y sincronización.

Para lograr una correcta cooperación entre procesos/hilos, el sistema operativo debe proporcionar medidas y métodos para lograr la cooperación de procesos.Hay dos métodos principales:

  • Bloqueo : operaciones de bloqueo y desbloqueo
  • Semáforo : operación P, V

Ambos pueden realizar fácilmente la exclusión mutua de procesos/subprocesos, y los semáforos son más potentes que los bloqueos, y también pueden realizar fácilmente la sincronización de procesos/subprocesos.

Cerrar

El uso de la operación de bloqueo y la operación de desbloqueo puede resolver el problema de exclusión mutua de subprocesos/procesos simultáneos.

Cualquier subproceso que quiera ingresar a la sección crítica debe primero realizar una operación de bloqueo. Si la operación de bloqueo se supera con éxito, el subproceso puede ingresar al área crítica; después de completar el acceso al recurso crítico, se realiza la operación de desbloqueo para liberar el recurso crítico.

 Dependiendo de la implementación del bloqueo, se puede dividir en [bloqueo en espera ocupado] y [bloqueo en espera no ocupado].

Implementación del bloqueo de espera ocupada

Antes de explicar la implementación del bloqueo de espera ocupado, debe comprender la instrucción de operación atómica especial proporcionada por la arquitectura de CPU moderna: instrucción Test-and-Set .

Si la instrucción Test-and-Set se expresa en código C, la forma es la siguiente:

La directiva test and set hace lo siguiente:

  • Actualice old_ptr al nuevo valor de new
  • devuelve el valor antiguo de old_ptr

La clave es que estos códigos se ejecutan de forma atómica . Debido a que puede probar el valor anterior y establecer el nuevo valor, llamamos a esta instrucción [probar y establecer].

Operaciones atómicas: o se ejecutan todas, o no se ejecuta ninguna, y no puede haber un estado intermedio que esté medio ejecutado .

Podemos usar el comando Test-and-Set para lograr [bloqueo de espera ocupado], el código es el siguiente:

 Entendamos por qué funciona este bloqueo:

  • Escenario 1: Primero suponga que se está ejecutando un subproceso, llamando a lock() , ningún otro subproceso mantiene el bloqueo, por lo que el indicador es 0. Cuando se llama al método TestAndSet(flag, 1) y se devuelve 0, el subproceso saltará fuera del ciclo while y adquirirá el bloqueo. Al mismo tiempo, la bandera se establecerá atómicamente en 1, lo que indica que el bloqueo ya se ha realizado. Cuando el subproceso abandona la sección crítica, llame a unlock() para borrar el indicador a 0.
  • Escenario 2: cuando un subproceso ya tiene un bloqueo (es decir, el indicador es 1). Este subproceso llama a lock() y luego llama a TestAndSet(flag, 1), que esta vez devuelve 1. Mientras otro subproceso mantenga el candado, TestAndSet() devolverá 1 repetidamente, y este subproceso estará ocupado esperando . Cuando el indicador finalmente se cambie a 0, el subproceso llamará a TestAndSet() , devolverá 0 y lo establecerá atómicamente en 1, adquiriendo así el bloqueo e ingresando a la sección crítica.

Obviamente, cuando no se puede obtener el bloqueo, el subproceso siempre se repetirá sin hacer nada, por lo que se denomina [bloqueo de espera ocupado], también conocido como bloqueo de giro .

Este es el tipo de bloqueo más simple, girando todo el tiempo, usando ciclos de CPU hasta que el bloqueo esté disponible. En un solo procesador, se requiere un programador preventivo (es decir, el reloj interrumpe constantemente un subproceso para ejecutar otros subprocesos). De lo contrario, los bloqueos de giro no se pueden usar en una sola CPU, porque un subproceso giratorio nunca abandonará la CPU. .

Implementación sin bloqueos de espera

Sin bloqueo en espera: cuando no se puede adquirir el bloqueo, no se requiere giro. Cuando no se adquiere el bloqueo, coloque el subproceso actual en la cola de espera de bloqueo, luego ejecute el programador y deje que la CPU sea ejecutada por otros subprocesos

Esta vez, solo se proponen dos implementaciones de bloqueos simples. Por supuesto, en un sistema operativo específico, será más complicado, pero también es inseparable de los dos elementos básicos de este ejemplo.

cantidad de señal

Un semáforo es un método proporcionado por el sistema operativo para coordinar el acceso a los recursos compartidos.

Por lo general, el semáforo representa el número de recursos y la variable correspondiente es una variable entera (sem).

Además, hay dos funciones de llamada del sistema de operación atómica para controlar la señal , a saber:

  • Operación P: decrementa sem en 1 , después de la resta, si sem < 0 , el proceso/hilo entra en espera de bloqueo, de lo contrario continúa, indicando que la operación P puede ser bloqueada;
  • Operación V: agregue 1 a sem , después de agregar, si sem <= 0, despierte un proceso/hilo en espera, lo que indica que la operación V no se bloqueará;

SUGERENCIA ¿Por qué sem <= 0 en la operación V?

Ejemplo: si sem = 1, hay tres hilos que realizan la operación P:

  • Después de que opera el primer subproceso P, sem = 0; el primer subproceso continúa ejecutándose
  • Después de la operación del segundo hilo P, sem = -1; sem < 0 El segundo hilo se bloquea y espera
  • Después de que opera el tercer subproceso P, sem = -2; sem < 0 El tercer subproceso se bloquea y espera

En este momento, después de que el primer subproceso ejecuta la operación V, sem=-1; sem <=0, por lo que es necesario despertar el segundo o tercer subproceso que está bloqueado y esperando.

La operación P es antes de entrar en el área crítica, y la operación V es después de salir del área crítica.Estas dos operaciones deben aparecer por pares .

Para dar una analogía, el semáforo de dos recursos es equivalente a dos vías de tren, y el proceso de operación de PV es el siguiente:

 ¿Cómo realiza el sistema operativo la operación fotovoltaica?

El algoritmo de la estructura de datos del semáforo y la operación de PV se describe a continuación:

Las funciones de las operaciones de PV son administradas e implementadas por el sistema operativo, por lo que el sistema operativo ha hecho que la ejecución de las funciones de PV sea atómica.

¿Cómo se utiliza la operación PV?

El semáforo no solo puede realizar el control de acceso de exclusión mutua de la sección crítica, sino también sincronizar los eventos entre hilos.

Semaphore realiza el acceso de exclusión mutua de la sección crítica

Se establece un semáforo s para cada tipo de recurso compartido , y su valor inicial es 1 , lo que indica que el recurso crítico no está ocupado.

Siempre que la operación que ingresa a la sección crítica se coloque entre P(s) y V(s) , la exclusión mutua de proceso/subproceso se puede realizar:

 En este momento, cualquier subproceso que quiera ingresar al área crítica primero debe realizar la operación P en el semáforo mutex y luego realizar la operación V después de completar el acceso al recurso crítico. Dado que el valor inicial del semáforo mutex es 1, el valor de s se convierte en 0 después de que el primer subproceso ejecuta la operación P, lo que indica que el recurso crítico está libre y se puede asignar al subproceso para ingresar a la sección crítica.

Si un segundo hilo quiere entrar en la sección crítica en este momento, también debe ejecutar primero la operación P, y el resultado hace que s se convierta en un valor negativo, lo que significa que el recurso crítico ha sido ocupado, por lo que el segundo hilo está bloqueado.

Y, hasta que el primer subproceso ejecuta la operación V, libera el recurso crítico y restaura el valor de s a 0, luego despierta el segundo subproceso y lo hace ingresar a la sección crítica. Después de que termina de acceder al recurso crítico, ejecuta la V operación de nuevo, de modo que s vuelve al valor inicial 1.

Para dos hilos concurrentes, el valor del semáforo mutex solo toma tres valores de 1, 0 y -1, que representan respectivamente:

  • Si el semáforo mutex es 1, significa que ningún subproceso entra en la sección crítica
  • Si el semáforo mutex es 0, significa que un hilo ha entrado en la sección crítica
  • Si el semáforo mutex es -1, significa que un subproceso ingresa a la sección crítica y otro subproceso espera para ingresar

A través del semáforo de exclusión mutua, se puede garantizar que solo se ejecuta un subproceso en la sección crítica en cualquier momento, y se logra el efecto de exclusión mutua.

Semaphore implementa la sincronización de eventos

La forma de sincronizar es establecer un semáforo cuyo valor inicial sea 0 .

 Cuando la madre le preguntó por primera vez a su hijo si quería cocinar, la ejecución fue equivalente a preguntar si el hijo necesitaba comer, como el valor inicial era 0, cambió a -1 en este momento, lo que indica que el hijo no necesitaba comer. eat, por lo que el subproceso madre entró en el estado de espera. P(s1)  s1  s1 

Cuando el hijo tiene hambre, se ejecuta , de manera que el semáforo cambia de -1 a 0, indicando que el hijo necesita comer en ese momento, por lo que se despierta el hilo madre bloqueado, y el hilo madre comienza a cocinar. V(s1) s1 

Luego se ejecuta el hilo hijo , lo que equivale a preguntarle a la madre si la comida ha terminado, como   el valor inicial es 0,   en este momento se convierte en -1, indicando que la madre no ha terminado de cocinar, y el hilo hijo está espera. P(s2)s2s2

Finalmente, la madre finalmente terminó de cocinar, así que lo ejecutó , y el semáforo cambió de -1 a 0, por lo que despertó el subproceso del hijo que esperaba. Después de despertarse, el subproceso del hijo puede comenzar a comer V(s2)s2 

problema productor-consumidor

 Descripción del problema productor-consumidor:

  • Después de que el productor genera datos, se colocan en un búfer
  • El consumidor obtiene datos del búfer para su procesamiento.
  • En cualquier momento, solo un productor o consumidor puede acceder al búfer

Del análisis del problema se puede concluir que:

  • Solo un subproceso puede operar el búfer en cualquier momento, lo que indica que la operación del búfer es un código crítico que requiere exclusión mutua ;
  • Cuando el búfer está vacío, el consumidor debe esperar a que el productor genere datos; cuando el búfer está lleno, el productor debe esperar a que el consumidor obtenga los datos. Explique que los productores y los consumidores necesitan estar sincronizados .

Entonces necesitamos tres semáforos, a saber:

  • Semáforo de exclusión mutua mutex : se utiliza para el acceso mutuamente exclusivo al búfer, el valor de inicialización es 1;
  • Semáforo de recursos fullBuffers : utilizado para que los consumidores pregunten si hay datos en el búfer y lean los datos si los hay, el valor inicial es 0 (lo que indica que el búfer está vacío al principio);
  • Semáforo de recursos emptyBuffers : lo utiliza el productor para preguntar si hay espacio en el búfer y, si hay espacio, se generarán los datos y el valor de inicialización es n (tamaño del búfer);

El código de implementación específico:

Si el subproceso del consumidor ejecuta P(fullBuffers)  al principio , dado que el valor inicial del semáforo fullBuffers es 0, el valor de fullBuffers cambia de 0 a -1 en este momento, lo que indica que no hay datos en el búfer y el El consumidor solo puede esperar.

Luego, es el turno del productor de ejecutar P(fullBuffers) , lo que significa reducir una ranura vacía. Si ningún otro subproceso de productor está ejecutando código en la sección crítica, entonces el subproceso de productor puede colocar los datos en el búfer. Después de colocarlo , ejecuta V(fullBuffers) , el semáforo fullBuffers cambia de -1 a 0, lo que indica que un subproceso de consumidor está bloqueando y esperando datos, por lo que se activará el subproceso de consumidor bloqueado en espera.

Después de que se activa el subproceso del consumidor, si ningún otro subproceso del consumidor está leyendo datos en este momento, puede ingresar directamente a la sección crítica y leer los datos del búfer. Finalmente, deje la sección crítica y agregue 1 al número de espacios vacíos.


problema clásico de sincronización

Problema de filósofos comedor

 Declaración del problema del filósofo:

  • 5 filósofos comiendo fideos alrededor de una mesa redonda
  • Esta mesa tiene solo 5 tenedores, se coloca un tenedor entre cada dos filósofos
  • Los filósofos no están pensando juntos, y quieren comer cuando tienen hambre durante el pensamiento.
  • Sin embargo, estos filósofos necesitan dos tenedores para estar dispuestos a comer fideos, es decir, necesitan tener los tenedores del lado izquierdo y derecho para comer.
  • Vuelva a colocar el tenedor después de comer

Pregunta: ¿Cómo garantizar que las acciones de los filósofos se lleven a cabo de manera ordenada, de modo que nadie obtenga el tenedor?

opcion uno

Usamos el método del semáforo, es decir, la operación PV para intentar resolver:

 El procedimiento anterior parece muy natural: toma el tenedor y usa P para operar, lo que significa usarlo directamente si hay un tenedor, y esperar a que otros filósofos vuelvan a colocar el tenedor si no hay tenedor.

 Sin embargo, hay un problema extremo con esta solución: suponiendo que cinco filósofos tomen los tenedores de la izquierda al mismo tiempo, no habrá tenedores en la mesa, por lo que nadie puede tomar los tenedores de su derecha, lo que significa que todo filósofo tendrá La afirmación P(fork[(i+1)%N]) está bloqueada, y es obvio que se ha producido un interbloqueo

Opción II

Dado que el plan 1 causará un interbloqueo causado por la competencia por la bifurcación izquierda al mismo tiempo, agregamos un semáforo mutex antes de tomar la bifurcación. El código es el siguiente:

 La función del semáforo de exclusión mutua en el programa anterior es que mientras un filósofo entre en el área crítica, es decir, cuando esté a punto de tomar un tenedor, los demás filósofos no podrán moverse .

La solución 2 permite que los filósofos coman en orden, pero solo un filósofo puede comer a la vez, y hay 5 tenedores en la mesa. Es razonable que dos filósofos puedan comer al mismo tiempo, por lo que desde la perspectiva de la eficiencia, que es no es la mejor solución.

tercera solucion

El problema con la Solución 1 es que existirá la posibilidad de que todos los filósofos puedan sostener el cuchillo y el tenedor izquierdos al mismo tiempo, por lo que podemos evitar que los filósofos tomen el cuchillo y el tenedor izquierdos al mismo tiempo, adoptar una estructura de rama y tomar diferentes acciones según el número de filósofos.

Es decir, que los filósofos pares [tomen primero la bifurcación izquierda y luego la bifurcación derecha], y los filósofos impares [tomen primero la bifurcación derecha y luego la bifurcación izquierda] .

 En el programa anterior, cuando se opera P, el orden de recoger las horquillas izquierda y derecha es diferente según el número del filósofo. Además, la operación V no requiere bifurcación, porque la operación V no bloquea.

 La opción tres significa que no habrá punto muerto y dos personas pueden comer al mismo tiempo

Opción cuatro

Aquí hay otra solución factible, usando un estado de matriz para registrar los tres estados de cada filósofo, que son el estado de alimentación, el estado de pensamiento y el estado de hambre (tratando de tomar un tenedor) .

Entonces un filósofo puede entrar en el estado de comer sólo si ambos vecinos no están comiendo .

Los vecinos del i-ésimo filósofo están definidos por las macros IZQUIERDA y DERECHA:

  • IZQUIERDA:(i+5-1) % 5
  • DERECHA:(i+1) % 5

Por ejemplo, si i es 2, IZQUIERDA es 1 y DERECHA es 3.

El código de implementación específico es el siguiente:

 El programa anterior utiliza una matriz de semáforos, uno para cada filósofo, de modo que los filósofos que quieren comer quedan bloqueados mientras el tenedor requerido está ocupado.

Tenga en cuenta que cada proceso/subproceso  smart_person ejecuta la función como el código principal, mientras que los demás   son   solo funciones normales, no procesos/subprocesos separados.take_forksput_forkstest

 problema lector-escritor

El problema Dining Philosophers anterior es útil para modelar procesos como problemas competitivos con acceso exclusivo limitado, como dispositivos de E/S.

Además, existe un conocido problema de "lector-escritor", que establece un modelo para el acceso a la base de datos.

Los lectores solo pueden leer datos y no modificarán datos, mientras que los escritores pueden leer y modificar datos.

Descripción del problema del lector-escritor:

  • "Leer-leer" permite: al mismo tiempo, varios lectores pueden leer al mismo tiempo
  • Exclusión mutua de "lectura-escritura": los lectores pueden leer cuando no hay escritor, y los escritores pueden escribir cuando no hay lectores
  • Exclusión mutua "escribir-escribir": un escritor solo puede escribir cuando no hay otros escritores

opcion uno

Utilice semáforos para resolver el problema:

  • Semáforo wMutex: Semáforo de exclusión mutua que controla la operación de escritura, el valor inicial es 1;
  • Reader count rCount: el número de lectores que están leyendo, inicializado a 0;
  • Semáforo rCountMutex: Controla la modificación mutuamente excluyente del punto contador del lector rCount, el valor inicial es 1;

Implementación del código:

 La implementación de lo anterior es una estrategia de lector primero, porque mientras haya lectores que estén leyendo, los lectores subsiguientes pueden entrar directamente, si los lectores siguen entrando, el escritor estará en un estado de inanición.

Opción II

Dado que existe una estrategia de lector primero, naturalmente existe una estrategia de escritor primero:

  • Siempre que un escritor esté listo para escribir, el escritor debe realizar la operación de escritura lo antes posible y los lectores posteriores deben bloquear
  • Los lectores se mueren de hambre si hay escritores que siguen escribiendo

Agregue las siguientes variables sobre la base del Esquema 1:

  • Semáforo rMutex : El semáforo de exclusión mutua que controla al lector para ingresar, el valor inicial es 1;
  • Semaphore wDataMutex : Semáforo de exclusión mutua que controla la operación de escritura del escritor, el valor inicial es 1;
  • Writer count wCount : registra el número de escritores, el valor inicial es 0;
  • Semáforo wCountMutex : Modificación de exclusión mutua de control wCount, el valor inicial es 1;

El código de implementación es el siguiente:

 Tenga en cuenta que el papel de rMutex aquí es que hay múltiples lectores leyendo datos al principio, y todos ingresan a la cola de lectores. En este momento, llega un escritor. Después de ejecutar P(rMutex) , los lectores posteriores no pueden ingresar porque están bloqueados. en rMutex La cola de lectores, y cuando llega el escritor, todos pueden ingresar a la cola de escritores, por lo que la prioridad del escritor está garantizada.

Al mismo tiempo, después de que el primer escritor ejecuta P(rMutex) , no puede comenzar a escribir inmediatamente. Debe esperar hasta que todos los lectores que ingresan a la cola de lectores hayan terminado de leer las operaciones y activar la operación de escritura del escritor a través de V(wDataMutex) .

tercera solucion

Dado que tanto la estrategia de primero el lector como la estrategia de primero el escritor causarán hambre, implementemos una estrategia justa.

Estrategia justa:

  • misma prioridad;
  • Escritores y lectores acceso mutuamente excluyente;
  • Solo un escritor puede acceder a la sección crítica;
  • Múltiples lectores pueden acceder a recursos críticos al mismo tiempo;

Implementación de código específico:

 Comparando la estrategia de lector primero del Esquema 1, se puede encontrar que en la estrategia de lector primero, siempre que lleguen lectores posteriores, los lectores pueden ingresar a la cola de lectores, mientras que los escritores deben esperar hasta que no llegue ningún lector.

Si no llega ningún lector, la cola de lectores estará vacía, es decir, rCount = 0. En este momento, el escritor puede ingresar a la sección crítica para realizar la operación de escritura.

El papel de la bandera aquí es evitar este permiso especial del lector (siempre que el lector llegue, puede ingresar a la cola del lector).

Por ejemplo: algunos lectores leen datos al principio y todos entran en la cola del lector. En este momento, viene un escritor y realiza una operación, de modo que los lectores posteriores se bloquean  y no pueden ingresar a la cola del lector, lo que hará que el lector gradualmente cola vacía. , que se   reduce a 0. P(falg) flag rCount

El escritor no puede comenzar a escribir inmediatamente (porque la cola del lector no está vacía en este momento), y se bloqueará en el semáforo. Después de que todos los lectores en la cola del lector hayan terminado de leer, se ejecuta el último proceso del lector , despierta al escritor justo ahora. y escribe De lo contrario, la operación de escritura continúa. wDataMutex  V(wDataMutex)

Supongo que te gusta

Origin blog.csdn.net/qq_48626761/article/details/132210855
Recomendado
Clasificación