Есть область памяти, допускающая чтение и запись. Несколько потоков имеют к ней доступ, при этом одновременно могут читать сколько угодно потоков, но писать — только один. Как обеспечить такой режим доступа?
Можно обойтись обычным мьютексом, но это не оптимально — компьютерная память, как правило, устроена так, что несколько потоков могут свободно читать и писать её (единственная проблема — нет гарантии, что в процессе обработки переменная внезапно не изменится). У этой проблемы есть несколько вариантов, разные и решения. Кому отдавать приоритет — читателю или писателю — остаётся за программистом.
Глобальные целые readcount=0, writecount=0;
Глобальные мьютексы mutex1, mutex2, w, r
ЧИТАТЕЛЬ
r.enter
mutex1.enter
readcount = readcount + 1
if readcount=1 then w.enter
mutex1.leave
r.leave
...читаем...
mutex1.enter
readcount = readcount - 1
if readcount = 0 then w.leave
mutex1.leave
ПИСАТЕЛЬ
mutex2.enter
writecount = writecount + 1
if writecount=1 then r.enter
mutex2.leave
w.enter
...пишем...
w.leave
mutex2.enter
writecount = writecount - 1
if writecount = 0 then r.leave
mutex2.leave
Третья задача о читателях-писателях (честное распределение ресурсов)
Не допускать простоев. Другими словами: независимо от действий других потоков, читатель или писатель должен пройти барьер за конечное время.
Иначе говоря, ни один поток (читатель или писатель) не должен ожидать захвата блокировки слишком долго; функция захвата блокировки должна по истечении некоторого времени, если захват не удался, вернуться с признаком захват не удался, чтобы поток не простаивал и мог заняться другими делами. Зачастую это время равно нулю: функция захвата, если захват невозможен прямо сейчас, сразу возвращается.
Глобальные мютексы: no_writers, no_readers, counter_mutex
Глобальные целые: nreaders=0
Локальные целые: prev, current
ПИСАТЕЛЬ:
no_writers.enter
no_readers.enter
...пишем....
no_writers.leave
no_readers.leave
ЧИТАТЕЛЬ:
no_writers.enter
counter_mutex.enter
preve:= nreaders
nreaders := nreaders + 1
if prev = 0 then no_readers.enter
counter_mutex.leave
no_writers.leave
...читаем...
counter_mutex.enter
nreaderst:= nreaders - 1;
currente:= nreaders;
if current = 0 then no_readers.leave
counter_mutex.leave;
Код на C для gcc
gcc -o rw -lpthread -lm rewr.c
#include<pthread.h>#include<stdio.h>#include<math.h>#include<stdlib.h>#include<semaphore.h>#define M 4 //num of WR#define N 3 //num of REunsignedintiter;//iterationsem_taccessM,readresM,orderM;//sem.unsignedintreaders=0;// Number of readers accessing the resourcevoid*reader(void*prm){intnum1=*(int*)prm;inti=0,r;for(i;i<iter;i++){if(sem_wait(&orderM)==0)printf("%d Читатель %d в очереди__________Ч%d\n",i,num1,num1);// Remember our order of arrivalsem_wait(&readresM);// We will manipulate the readers counterif(readers==0)// If there are currently no readers (we came first)...sem_wait(&accessM);// ...requests exclusive access to the resource for readersreaders++;// Note that there is now one more readersem_post(&orderM);// Release order of arrival semaphore (we have been served)sem_post(&readresM);// We are done accessing the number of readers for nowprintf("%d Работает читатель %d________________Ч%d\n",i,num1,num1);// Here the reader can read the resource at willr=1+rand()%4;sleep(r);sem_wait(&readresM);// We will manipulate the readers counterreaders--;// We are leaving, there is one less readerif(readers==0)// If there are no more readers currently reading...sem_post(&accessM);// ...release exclusive access to the resourcesem_post(&readresM);// We are done accessing the number of readers for now}}void*writer(void*prm){intnum2=*(int*)prm;intj=0,r;for(j;j<iter;j++){if(sem_wait(&orderM)==0)printf("%d Писатель %d в очереди__________П%d\n",j,num2,num2);// Remember our order of arrivalsem_wait(&accessM);// Request exclusive access to the resourcesem_post(&orderM);// Release order of arrival semaphore (we have been served)printf("%d Работает писатель %d________________П%d\n",j,num2,num2);// Here the writer can modify the resource at willr=1+rand()%4;sleep(r);sem_post(&accessM);// Release exclusive access to the resource}}voidmain(){pthread_tthreadRE[N];pthread_tthreadWR[M];sem_init(&accessM,0,1);sem_init(&readresM,0,1);sem_init(&orderM,0,1);printf("Введите количество итераций: ");scanf("%d",&iter);printf("Iter ОЧЕРЕДЬ/ВЫПОЛНЕНИЕ\n");inti;for(i=0;i<M;i++){pthread_create(&(threadWR[i]),NULL,writer,(void*)&i);}for(i=0;i<N;i++){pthread_create(&(threadRE[i]),NULL,reader,(void*)&i);}for(i=0;i<N;i++){pthread_join(threadRE[i],NULL);}for(i=0;i<M;i++){pthread_join(threadWR[i],NULL);}sem_destroy(&accessM);sem_destroy(&readresM);sem_destroy(&orderM);}
Инвариант
Зачастую инвариантом этой задачи считают Много читателей XOR один писатель (XOR — исключающее или). Это неверно, поскольку ситуация, когда нет ни читателей, ни писателей, также является корректной.
Инвариант можно выразить следующим утверждением:
writers == 0 OR writers == 1 AND readers == 0
где writers — число писателей, readers — число читателей.
Применение в программировании
Часто простой мьютекс, защищающий память, неоптимален. Например, в онлайн-игре список игровых комнат изменяется нечасто — когда кто-то из игроков решает открыть новую комнату, например, раз в несколько секунд. Считывается же он за одну секунду десятки раз, и выстраивать клиентов в очередь для этого не имеет смысла.
Подобные механизмы (так называемая блокировка чтения-записи) существуют во многих языках программирования и библиотеках. Например.
↑Communications of the ACM :Concurrent Control with «Readers» and «Writers» P.J. Courtois,* F. H, 1971 [1]Архивная копия от 7 марта 2012 на Wayback Machine