POSIX Threads/Synchronizacja między wątkami/Blokady zapis odczyt

WstępEdytuj

Mutexy 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 destrukcjaEdytuj

Blokadę tworzy funkcja pthread_rwlock_init, natomiast niszczy pthread_rwlock_destroy.

TypyEdytuj

  • pthread_rwlock_t
  • pthread_rwlockattr_t

FunkcjeEdytuj

  • pthread_rwlock_init  (doc)
  • pthread_rwlock_destroy  (doc)

Atrybuty blokadyEdytuj

Atrybuty 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.

TypyEdytuj

  • pthread_rwlockattr_t

FunkcjeEdytuj

  • 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 odczytuEdytuj

FunkcjeEdytuj

  • 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 zapisuEdytuj

FunkcjeEdytuj

  • 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 blokadyEdytuj

Niezależnie od rodzaju pozyskanej blokady, wątek zwalnia ją funkcją pthread_rwlock_unlock.

FunkcjeEdytuj

  • int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)  (doc)

PrzykładEdytuj

Przykł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ść