El problema del lector-escritor

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 27 de mayo de 2013; las comprobaciones requieren 23 ediciones .

El problema lector-escritor  es uno de los problemas más importantes en la programación paralela . Está formulado así:

Hay un área de memoria que se puede leer y escribir. Varios hilos tienen acceso a él, mientras que tantos hilos como quieran pueden leer al mismo tiempo, pero solo uno puede escribir. ¿Cómo proporcionar dicho modo de acceso?

Puede arreglárselas con un mutex ordinario , pero esto no es óptimo: la memoria de la computadora generalmente está diseñada para que varios subprocesos puedan leerla y escribirla libremente (el único problema es que no hay garantía de que durante el procesamiento la variable no cambie repentinamente) . Este problema tiene varias opciones, diferentes y soluciones. A quién dar prioridad, el lector o el escritor, permanece con el programador.

El primer problema lector-escritor (prioridad lector)

Mientras la memoria esté abierta para lectura, proporcione a los lectores acceso sin restricciones. Los escritores pueden esperar todo el tiempo que quieran.

El segundo problema lector-escritor (prioridad del escritor)

Tan pronto como aparecía al menos un escritor, no se permitía la entrada a nadie más. Todos los demás pueden estar inactivos.

Solución de muestra [1] :

Números enteros globales readcount=0, writecount=0; Mutexes globales mutex1, mutex2, w, r LECTOR inquilino mutex1.enter recuento = recuento de lecturas + 1 si recuento = 1 entonces w.enter mutex1.dejar r.dejar ...lectura... mutex1.enter recuento = recuento de lecturas - 1 si recuento = 0 entonces w.leave mutex1.dejar ESCRITOR mutex2.enter número de escrituras = número de escrituras + 1 si writecount=1 entonces r.enter mutex2.leave w.enter ...nosotros escribimos... con licencia mutex2.enter número de escrituras = número de escrituras - 1 si writecount = 0 entonces r.leave mutex2.leave

El tercer problema lector-escritor (distribución justa de recursos)

Evite el tiempo de inactividad. En otras palabras: independientemente de las acciones de otros hilos, el lector o escritor debe pasar la barrera en un tiempo finito.

En otras palabras, ningún subproceso (lector o escritor) debería esperar demasiado para adquirir un bloqueo; la función de captura de bloqueo debería, después de un tiempo, si el bloqueo falla, regresar con el indicador de bloqueo fallido para que el subproceso no esté inactivo y pueda hacer otras cosas. A menudo, este tiempo es cero: la función de captura, si la captura no es posible en este momento, regresa inmediatamente.

Mutexes globales: no_writers, no_readers, counter_mutex Enteros globales: nreaders=0 Enteros locales: anterior, actual ESCRITOR: no_escritores.ingresar no_lectores.ingresar ... escritura .... no_writers.leave no_readers.leave LECTOR: no_escritores.ingresar counter_mutex.enter prevenir:= n lectores nlectores := nlectores + 1 si anterior = 0 entonces no_readers.enter contador_mutex.dejar no_writers.leave ...lectura... counter_mutex.enter nreaderst:= nreaders - 1; currente:= nlectores; si actual = 0 entonces no_readers.leave contador_mutex.salir;

Código C para gcc gcc -o rw -lpthread -lm rewr.c

#incluir <pthread.h> #incluir <stdio.h> #incluir <matemáticas.h> #incluir <stdlib.h> #include <semáforo.h> #define M 4 //numero de WR #define N 3 //numero de RE unsigned int iter ; //iteración sem_t accessM , readresM , orderM ; // sem. lectores int sin firmar = 0 ; // Número de lectores que acceden al recurso vacío * lector ( vacío * prem ) { int num1 =* ( int * ) prm ; int i = 0 , r ; para ( i ; i < iter ; i ++ ) { if ( sem_wait ( & orderM ) == 0 ) printf ( "%d Lector %d en cola__________W%d \n " , i , num1 , num1 ); // Recuerda nuestro orden de llegada sem_wait ( & readresM ); // Manipularemos el contador de lectores si ( lectores == 0 ) // Si actualmente no hay lectores (nosotros llegamos primero)... sem_wait ( & accessM ); // ...solicita acceso exclusivo al recurso para lectores lectores ++ ; // Tenga en cuenta que ahora hay un lector más sem_post ( & orderM ); // Liberar orden de llegada del semáforo (nos han servido) sem_post ( & readresM ); // Hemos terminado de acceder al número de lectores por ahora printf ( "%d Lector %d________________W%d está funcionando \n " , i , num1 , num1 ); // Aquí el lector puede leer el recurso a su antojo r = 1 + rand () % 4 ; dormir ( r ); sem_esperar ( & readresM ); // Manipularemos el contador de lectores lectores -- ; // Nos vamos, hay un lector menos if ( lectores == 0 ) // Si no hay más lectores leyendo actualmente... sem_post ( & accessM ); // ...liberar acceso exclusivo al recurso sem_post ( & readresM ); // Hemos terminado de acceder al número de lectores por ahora } } vacío * escritor ( vacío * prem ) { int num2 =* ​​( int * ) prm ; int j = 0 , r ; para ( j ; j < iter ; j ++ ) { if ( sem_wait ( & orderM ) == 0 ) printf ( "%d Writer %d en cola__________P%d \n " , j , num2 , num2 ); // Recuerda nuestro orden de llegada sem_wait ( & accessM ); // Solicitar acceso exclusivo al recurso sem_post ( & orderM ); // Liberar semaforo de orden de llegada (nos han servido) printf ( "%d Escritor %d________________P%d \n " , j , num2 , num2 ); // Aquí el escritor puede modificar el recurso a su antojo r = 1 + rand () % 4 ; dormir ( r ); sem_post ( & accesoM ); // Liberar acceso exclusivo al recurso } } vacío principal () { pthread_t threadRE [ N ]; pthread_t threadWR [ M ]; sem_init ( & accessM , 0 , 1 ); sem_init ( & readresM , 0 , 1 ); sem_init ( & orderM , 0 , 1 ); printf ( "Ingrese el número de iteraciones: " ); scanf ( "%d" , & iter ); printf ( "Iter COLA/EJECUCION \n " ); ent i ; para ( yo = 0 ; yo < M ; yo ++ ) { pthread_create ( & ( threadWR [ i ]), NULL , escritor ,( void * ) & i ); } para ( yo = 0 ; yo < N ; yo ++ ) { pthread_create ( & ( threadRE [ i ]), NULL , lector ,( void * ) & i ); } para ( yo = 0 ; yo < N ; yo ++ ) { pthread_join ( threadRE [ i ], NULL ); } para ( yo = 0 ; yo < M ; yo ++ ) { pthread_join ( threadWR [ i ], NULL ); } sem_destroy ( & accessM ); sem_destroy ( & readresM ); sem_destroy ( & orderM ); }

Invariante

Muchos lectores XOR un escritor (XOR exclusivo o ) a menudo se considera una invariante de este problema . Esto no es cierto, ya que la situación en la que no hay lectores ni escritores también es correcta.

El invariante se puede expresar mediante el siguiente enunciado:

escritores == 0 O escritores == 1 Y lectores == 0

donde escritores es el número de escritores, lectores es el número de lectores.

Aplicaciones en programación

A menudo, un mutex de protección de memoria simple es subóptimo. Por ejemplo, en un juego en línea, la lista de salas de juego cambia con poca frecuencia, cuando uno de los jugadores decide abrir una nueva sala, por ejemplo, una vez cada pocos segundos. Se lee docenas de veces en un segundo y no tiene sentido buscar clientes para esto .

Mecanismos similares (los llamados bloqueos de lectura y escritura ) existen en muchos lenguajes de programación y bibliotecas. Por ejemplo.

  • Embarcadero Delfos : IMultiReadExclusiveWrite.
  • POSIX : pthread_rwlock_t.
  • JAVA : ReadWriteLock, ReentrantReadWriteLock.
  • Marco .NET : System.Threading.ReaderWriterLockSlim.
  • C++ : std::shared_mutex(desde C++17 [2] , antes de boost::shared_mutex).

Véase también

Notas

  1. Comunicaciones de la ACM: control concurrente con "lectores" y "escritores" PJ Courtois,* F. H, 1971 [1] Archivado el 7 de marzo de 2012 en Wayback Machine .
  2. std::  shared_mutex-cppreference.com . es.cppreference.com. Consultado el 13 de abril de 2018. Archivado desde el original el 14 de abril de 2018.