POSIX Threads/Synchronizacja między wątkami/Blokady zapis odczyt
Wstęp
edytujMutexy czy wirujące blokady umożliwiają ochronę współdzielonych zasobów jednakowo traktując wątki zmieniające ten obiekt jak i wątki jedynie czytające - dopuszczając do obiektu tylko jeden wątek.
Blokady zapis/odczyt (rwlocks) rozróżniają cel dostępu do obiektu współdzielonego - wątek może założyć blokadę do odczytu (rdlock) lub do zapisu (wrlock). Dowolna liczba wątków może mieć jednoczesny dostęp do obiektu chronionego, jeśli tylko zakładają blokadę do odczytu, natomiast dokładnie jeden wątek ma dostęp do obiektu, gdy założy blokadę do zapisu.
Inicjalizacja i destrukcja
edytujBlokadę tworzy funkcja pthread_rwlock_init, natomiast niszczy pthread_rwlock_destroy.
Typy
edytuj- pthread_rwlock_t
- pthread_rwlockattr_t
Funkcje
edytujAtrybuty blokady
edytujAtrybuty blokady tworzy funkcja pthread_rwlockattr_init, natomiast niszczy pthread_rwlockattr_destroy.
Jeśli biblioteka implementuje opcję THS, wówczas blokada ma tylko jeden parametr: flagę współdzielenia między procesami, którą ustawia pthread_rwlockattr_setpshared, zaś odczytuje pthread_rwlockattr_getpshared.
Typy
edytuj- pthread_rwlockattr_t
Funkcje
edytuj- pthread_rwlockattr_init(pthread_rwlockattr_t *attr) (doc)
- incjalizacja
- pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr) (doc)
- zniszczenie
- pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) (doc)
- ustawienie flagi, pshared ma wartość PTHREAD_PROCESS_PRIVATE lub PTHREAD_PROCESS_SHARED
- pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int pshared (doc)
- odczyt flagi
Blokady do odczytu
edytujFunkcje
edytuj- pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) (doc)
- oczekiwanie na pozyskanie blokady w nieskończoność
- pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock) (doc)
- bez oczekiwania na pozyskanie blokady
- pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abs_timeout) (doc)
- oczekiwanie na pozyskanie blokady ograniczone czasowo (dostępne jeśli biblioteka implementuje opcję TMO)
Blokady do zapisu
edytujFunkcje
edytuj- pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) (doc)
- oczekiwanie na pozyskanie blokady w nieskończoność
- pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock) (doc)
- bez oczekiwania na pozyskanie blokady
- pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abs_timeout) (doc)
- oczekiwanie na pozyskanie blokady ograniczone czasowo (dostępne jeśli biblioteka implementuje opcję TMO)
Zwalnianie blokady
edytujNiezależnie od rodzaju pozyskanej blokady, wątek zwalnia ją funkcją pthread_rwlock_unlock.
Funkcje
edytuj- int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) (doc)
Przykład
edytujPrzykładowy program tworzy kilka wątków piszących, a więc zakładających blokady do zapisu, oraz więcej wątków czytających, zakładających blokady do odczytu.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
void ms_sleep(const unsigned ms);
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
pthread_rwlock_t blokada;
int wartosc; // obiekt chroniony blokadą
/* wątek zmienia wartość */
void* pisarz(void* numer) {
while (1) {
printf(" pisarz #%d czeka na dostęp\n", (int)numer);
errno = pthread_rwlock_wrlock(&blokada);
test_errno("pthread_rwlock_wrlock");
printf(" pisarz #%d ustawia nową wartość\n", (int)numer);
ms_sleep(113);
printf(" pisarz #%d zwalnia blokadę\n", (int)numer);
errno = pthread_rwlock_unlock(&blokada);
test_errno("pthread_rwlock_unlock");
ms_sleep(317);
}
return NULL;
}
//------------------------------------------------------------------------
/* wątek tylko odczytuje wartość */
void* czytelnik(void* numer) {
int errno;
while (1) {
printf(" czytelnik #%d czeka na dostęp\n", (int)numer);
errno = pthread_rwlock_rdlock(&blokada);
test_errno("pthread_rwlock_rdlock");
printf(" czytelnik #%d odczytuje wartość\n", (int)numer);
ms_sleep(13);
printf(" czytelnik #%d zwalnia blokadę\n", (int)numer);
errno = pthread_rwlock_unlock(&blokada);
test_errno("pthread_rwlock_unlock");
ms_sleep(13);
}
return NULL;
}
//------------------------------------------------------------------------
#define N 5 /* liczba wątków */
#define K 2
int main() {
pthread_t id;
int i;
pthread_rwlock_init(&blokada, NULL);
/* utworzenie K wątków piszących */
for (i=0; i < K; i++) {
errno = pthread_create(&id, NULL, pisarz, (void*)i);
test_errno("pthread_create");
}
/* utworzenie N wątków czytających */
for (i=0; i < N; i++) {
errno = pthread_create(&id, NULL, czytelnik, (void*)i);
test_errno("pthread_create");
}
/* kilka sekund na pracę wątków i koniec */
ms_sleep(1500);
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
void ms_sleep(const unsigned ms) {
struct timespec req;
req.tv_sec = (ms / 1000);
req.tv_nsec = (ms % 1000 * 1000000);
nanosleep(&req, NULL);
}
//------------------------------------------------------------------------
Przykładowy wynik:
$ ./przyklad pisarz #0 czeka na dostęp pisarz #0 ustawia nową wartość pisarz #1 czeka na dostęp czytelnik #0 czeka na dostęp czytelnik #1 czeka na dostęp czytelnik #3 czeka na dostęp czytelnik #2 czeka na dostęp czytelnik #4 czeka na dostęp pisarz #0 zwalnia blokadę pisarz #1 ustawia nową wartość pisarz #1 zwalnia blokadę czytelnik #0 odczytuje wartość czytelnik #4 odczytuje wartość czytelnik #1 odczytuje wartość czytelnik #3 odczytuje wartość czytelnik #2 odczytuje wartość czytelnik #4 zwalnia blokadę czytelnik #1 zwalnia blokadę czytelnik #2 zwalnia blokadę czytelnik #0 zwalnia blokadę czytelnik #3 zwalnia blokadę czytelnik #3 czeka na dostęp czytelnik #3 odczytuje wartość czytelnik #0 czeka na dostęp czytelnik #0 odczytuje wartość czytelnik #2 czeka na dostęp czytelnik #2 odczytuje wartość czytelnik #1 czeka na dostęp czytelnik #1 odczytuje wartość czytelnik #4 czeka na dostęp czytelnik #4 odczytuje wartość czytelnik #2 zwalnia blokadę czytelnik #3 zwalnia blokadę czytelnik #4 zwalnia blokadę czytelnik #0 zwalnia blokadę czytelnik #1 zwalnia blokadę pisarz #0 czeka na dostęp pisarz #0 ustawia nową wartość czytelnik #2 czeka na dostęp czytelnik #3 czeka na dostęp czytelnik #4 czeka na dostęp czytelnik #1 czeka na dostęp czytelnik #0 czeka na dostęp pisarz #0 zwalnia blokadę czytelnik #2 odczytuje wartość czytelnik #4 odczytuje wartość pisarz #1 czeka na dostęp czytelnik #0 odczytuje wartość czytelnik #3 odczytuje wartość czytelnik #1 odczytuje wartość czytelnik #1 zwalnia blokadę czytelnik #3 zwalnia blokadę czytelnik #0 zwalnia blokadę czytelnik #2 zwalnia blokadę czytelnik #4 zwalnia blokadę pisarz #1 ustawia nową wartość