Problema cititor-scriitor

Versiunea actuală a paginii nu a fost încă revizuită de colaboratori experimentați și poate diferi semnificativ de versiunea revizuită la 27 mai 2013; verificările necesită 23 de modificări .

Problema cititor-scriitor  este una dintre cele mai importante probleme în programarea paralelă . Este formulat astfel:

Există o zonă de memorie care poate fi citită și scrisă. Mai multe fire au acces la el, în timp ce câte fire doresc pot citi în același timp, dar doar unul poate scrie. Cum se oferă un astfel de mod de acces?

Vă puteți descurca cu un mutex obișnuit , dar acest lucru nu este optim - memoria computerului este de obicei proiectată astfel încât mai multe fire să o poată citi și scrie liber (singura problemă este că nu există nicio garanție că în timpul procesării variabilei nu se va schimba brusc) . Această problemă are mai multe opțiuni, diferite și soluții. Cui să-i acorde prioritate – cititorului sau scriitorului – rămâne la programator.

Prima problemă cititor-scriitor (prioritatea cititorului)

În timp ce memoria este deschisă pentru citire, oferiți cititorilor acces nerestricționat. Scriitorii pot aștepta cât doresc.

A doua problemă cititor-scriitor (prioritate scriitor)

De îndată ce a apărut cel puțin un scriitor, nimeni altcineva nu avea voie să intre. Toți ceilalți pot fi inactiv.

Probă de soluție [1] :

Număr de numere întregi globale readcount=0, writecount=0; Mutexuri globale mutex1, mutex2, w, r CITITOR r.intra mutex1.enter readcount = readcount + 1 dacă readcount=1 atunci w.enter mutex1.pleaca r.pleaca ...citind... mutex1.enter readcount = readcount - 1 dacă readcount = 0 atunci w.leave mutex1.pleaca SCRIITOR mutex2.enter număr de scrieri = număr de scrieri + 1 dacă writecount=1 atunci r.enter mutex2.pleaca w.intra ...noi scriem... w.pleaca mutex2.enter număr de scrieri = număr de scrieri - 1 dacă writecount = 0 atunci r.leave mutex2.pleaca

A treia problemă cititor-scriitor (distribuirea echitabilă a resurselor)

Evitați timpul de nefuncționare. Cu alte cuvinte: indiferent de acțiunile altor fire, cititorul sau scriitorul trebuie să treacă bariera în timp finit.

Cu alte cuvinte, niciun thread (cititor sau scriitor) nu ar trebui să aștepte prea mult pentru a obține o blocare; funcția de capturare a blocării ar trebui, după ceva timp, dacă blocarea eșuează, să revină cu semnalizarea blocării eșuate , astfel încât firul să nu fie inactiv și să poată face alte lucruri. Adesea, acest timp este zero: funcția de captare, dacă capturarea nu este posibilă acum, revine imediat.

Mutexuri globale: no_writers, no_readers, counter_mutex Numerele întregi globale: nreaders=0 Numere întregi locale: anterior, curent SCRIITOR: no_writers.enter no_cititori.enter ... scris .... nu_scriitori.pleacă nu_cititori.pleaca CITITOR: no_writers.enter counter_mutex.enter preve:= nreaders nreaders := nreaders + 1 dacă prev = 0 atunci no_readers.enter counter_mutex.leave nu_scriitori.pleacă ...citind... counter_mutex.enter nreaderst:= nreaders - 1; curent:= nreaders; dacă curent = 0 atunci no_cititori.pleacă counter_mutex.leave;

Cod C pentru gcc gcc -o rw -lpthread -lm rewr.c

#include <pthread.h> #include <stdio.h> #include <math.h> #include <stdlib.h> #include <semafor.h> #define M 4 //num of WR #define N 3 //num of RE unsigned int iter ; //iterație sem_t accessM , readresM , orderM ; //sem. unsigned int cititori = 0 ; // Numărul de cititori care accesează resursa void * cititor ( void * prem ) { int num1 =* ( int * ) prm ; int i = 0 , r ; pentru ( i ; i < iter ; i ++ ) { if ( sem_wait ( & orderM ) == 0 ) printf ( "%d Reader %d coada__________W%d \n " , i , num1 , num1 ); // Amintiți-vă ordinea noastră de sosire sem_wait ( & readresM ); // Vom manipula contorul de cititori if ( readers == 0 ) // Dacă în prezent nu există cititori (noi am venit primii)... sem_wait ( & accessM ); // ...solicită acces exclusiv la resursă pentru cititori cititori ++ ; // Rețineți că acum mai există un cititor sem_post ( & orderM ); // Eliberează ordinea de sosire a semaforului (ne-am servit) sem_post ( & readresM ); // Am terminat de accesat numărul de cititori deocamdată printf ( "%d Reader %d________________W%d funcționează \n " , i , num1 , num1 ); // Aici cititorul poate citi resursa după bunul plac r = 1 + rand () % 4 ; somn ( r ); sem_wait ( & readresM ); // Vom manipula cititorii contor cititori -- ; // Plecăm, este un cititor mai puțin dacă ( cititori == 0 ) // Dacă nu mai sunt cititori care citesc în prezent... sem_post ( & accessM ); // ...eliberează accesul exclusiv la resursa sem_post ( & readresM ); // Am terminat de accesat numărul de cititori pentru moment } } void * scriitor ( void * prem ) { int num2 =* ​​( int * ) prm ; int j = 0 , r ; pentru ( j ; j < iter ; j ++ ) { if ( sem_wait ( & orderM ) == 0 ) printf ( "%d Writer %d coada__________P%d \n " , j , num2 , num2 ); // Amintiți-vă ordinea noastră de sosire sem_wait ( & accessM ); // Solicită acces exclusiv la resursa sem_post ( & orderM ); // Eliberarea semaforului pentru ordinea de sosire (ne-a fost servit) printf ( "%d Writer %d________________P%d \n " , j , num2 , num2 ); // Aici scriitorul poate modifica resursa după bunul plac r = 1 + rand () % 4 ; somn ( r ); sem_post ( & accessM ); // Eliberează accesul exclusiv la resursă } } void main () { pthread_t threadRE [ N ]; pthread_t threadWR [ M ]; sem_init ( & accessM , 0 , 1 ); sem_init ( & readresM , 0 , 1 ); sem_init ( & orderM , 0 , 1 ); printf ( "Introduceți numărul de iterații: " ); scanf ( "%d" , & iter ); printf ( "Iter COADA/EXECUȚIE \n " ); int i ; pentru ( i = 0 ; i < M ; i ++ ) { pthread_create ( & ( threadWR [ i ]), NULL , writer ,( void * ) & i ); } pentru ( i = 0 ; i < N ; i ++ ) { pthread_create ( & ( threadRE [ i ]), NULL , cititor ,( void * ) & i ); } pentru ( i = 0 ; i < N ; i ++ ) { pthread_join ( threadRE [ i ], NULL ); } pentru ( i = 0 ; i < M ; i ++ ) { pthread_join ( threadWR [ i ], NULL ); } sem_destroy ( & accessM ); sem_destroy ( & readresM ); sem_destroy ( & orderM ); }

Invariant

Mulți cititori XOR un singur scriitor (XOR - exclusiv sau ) este adesea considerat un invariant al acestei probleme . Acest lucru nu este adevărat, deoarece situația în care nu există nici cititori, nici scriitori este și ea corectă.

Invariantul poate fi exprimat prin următoarea afirmație:

scriitori == 0 SAU scriitori == 1 ȘI cititori == 0

unde scriitori este numărul de scriitori, cititorii este numărul de cititori.

Aplicații în programare

Adesea, un simplu mutex care protejează memoria este suboptim. De exemplu, într- un joc online, lista sălilor de joc se schimbă rar - atunci când unul dintre jucători decide să deschidă o cameră nouă, de exemplu, o dată la câteva secunde. Este citit de zeci de ori într-o secundă și nu are sens să aliniați clienții pentru asta .

Mecanisme similare (așa-numita blocare citire-scriere ) există în multe limbaje de programare și biblioteci. De exemplu.

  • Embarcadero Delphi : IMultiReadExclusiveWrite.
  • POSIX : pthread_rwlock_t.
  • Java : ReadWriteLock, ReentrantReadWriteLock.
  • .NET Framework : System.Threading.ReaderWriterLockSlim.
  • C++ : std::shared_mutex(din C++17 [2] , înainte de acel boost::shared_mutex).

Vezi și

Note

  1. Communications of the ACM: Concurrent Control with "Readers" and "Writers" PJ Courtois,* F. H, 1971 [1] Arhivat 7 martie 2012 la Wayback Machine
  2. std::shared_mutex -  cppreference.com . en.cppreference.com. Consultat la 13 aprilie 2018. Arhivat din original pe 14 aprilie 2018.