POSIX Threads/Synchronizacja między wątkami/Zmienne warunkowe

Wstęp

edytuj

Zmienna warunkowa (condition variable) jest jednym ze sposobów synchronizacji między wątkami - polega na przesłaniu sygnału z jednego wątku do innych wątków, które na ten sygnał oczekują.

Prościej rzecz ujmując jeden lub kilka wątków może oczekiwać na zajście jakiegoś warunku, inny wątek, gdy go spełni, sygnalizuje właśnie poprzez zmienną warunkową ten fakt jednemu lub wszystkim oczekującym. We wzorcu producent-konsument występuje właśnie taka sytuacja: konsument (jeden lub więcej) czeka na pojawienie się obiektów od producenta (jednego lub więcej).

Zmienna warunkowa jest zawsze używana z mutexem.

Typem danych, który opisuje zmienną warunkową jest pthread_cond_t.

Schemat użycia zmiennej warunkowej podczas oczekiwania

edytuj
  1. pozyskaj blokadę (mutex)
  2. sprawdź, czy warunek zaszedł
  3. jeśli tak, zwolnij blokadę
  4. w przeciwnym razie oczekuj na sygnał (w tym miejscu blokada jest automatycznie zwalniana)
    • sygnał nadszedł, przejdź do punktu 2 (w tym miejscu blokada jest automatycznie ponownie pozyskiwana)

Co w języku C wygląda mniej więcej tak:

pthread_mutex_lock(&mutex);
do {
	if (warunek spełniony) {
		/* ... */
		break;
	}
	else
		pthread_cond_wait(&cond, &mutex);
} while (1);
pthread_mutex_unlock(&mutex);

Inicjalizacja zmiennej warunkowej

edytuj

Funkcją pthread_cond_init inicjuje zmienną warunkową, umożliwia również przypisanie atrybutów. Istnieje możliwość statycznej inicjalizacji wartością PTHREAD_COND_INITIALIZER.

Każda zmienna warunkowa, bez względu na sposób inicjalizacji, musi zostać zwolniony funkcja pthread_cond_destroy. Implementacja biblioteki może bowiem PTHREAD_COND_INITIALIZER realizować poprzez wywołanie jakiejś funkcji, która np. alokuje pamięć i nie zwolnienie zmiennej doprowadzi do wycieku pamięci.

pthread_cond_t	cond	= PTHREAD_COND_INITIALIZER;
pthread_cond_attr_t attr;

/* ... */
pthread_cond_init(&cond, NULL);		/* inicjalizacja zmiennej warunkowej z atrybutami domyślnymi */

/* ... */
pthread_condattr_init(&attr);		/* inicjalizacja atrybutów zmiennej */
pthread_cond_init(&cond, &attr);	/* inicjalizacja zmiennej warunkowej z atrybutami użytkownika */

/* ... */
pthread_cond_destroy(&cond);		/* skasowanie zmiennej warunkowej */
pthread_condattr_destroy(&attr);	/* oraz atrybutów */
  • pthread_cond_t
zmienna warunkowa
  • pthread_condattr_t
atrybuty zmiennej warunkowej

Funkcje

edytuj
  • int pthread_cond_init(pthread_cond_t *cond, cond pthread_condattr_t *attr)  (doc)
inicjalizacja zmiennej warunkowej, wskaźnik na atrybuty attr może być pusty
  • int pthread_cond_destroy(pthread_cond_t *cond)  (doc)
zwolenie zmiennej warunkowej

Atrybuty

edytuj

Zmienna warunkowa może mieć dwa atrybuty, zależnie od rozszerzeń:

  • THS - czy zmienna warunkowa może być współdzielona między procesami (domyślnie nie);
  • CT - ustawienie identyfikatora zegara, który jest używany przy odmierzaniu czasu w funkcji pthread_cond_timedwait
  • pthread_condattr_t
atrybuty zmiennej warunkowej

Funkcje

edytuj
  • int pthread_condattr_init(pthread_condattr_t *attr)  (doc)
inicjalizacja atrybutów zmiennej warunkowej
  • int pthread_condattr_destroy(pthread_condattr_t *attr)  (doc)
zwolenienie atrybutów zmiennej warunkowej
  • int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared)  (doc)
ustawienie flagi współdzielenia z innymi procesami; pshared ma wartość PTHREAD_PROCESS_SHARED lub PTHREAD_PROCESS_PRIVATE (domyślnie)
  • int pthread_condattr_getpshared(pthread_condattr_t *attr, int *pshared)  (doc)
odczyt flagi
  • int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id)  (doc)
ustawienie identyfikatora zegara
  • int pthread_condattr_getclock(pthread_condattr_t *attr, clockid_t *clock_id)  (doc)
odczyt identyfikatora

Sygnalizowanie i rozgłaszanie

edytuj

Wątek, który zmienił warunek informuje o tym fakcie oczekujące wątki na dwa sposoby:

  • sygnalizując (pthread_cond_signal) - sygnał trafia do jednego z oczekujących wątków, przy czym nie jest określone do którego;
  • rozgłaszając (pthread_cond_broadcast) - sygnał trafia do wszystkich oczekujących wątków.

Funkcje

edytuj
  • int pthread_cond_signal(pthread_cond_t *cond)  (doc)
  • int pthread_cond_broadcast(pthread_cond_t *cond)  (doc)

Oczekiwanie

edytuj

Oczekiwanie na zmienną warunkową umożliwiają dwie funkcje:

  1. pthread_cond_wait - oczekuje w nieskończoność na sygnał,
  2. pthread_cond_timedwait - oczekuje przez ograniczony czas.

Sposób obsługi czasu w pthreads został opisany dokładnie przy okazji pthread_mutex_timedwait (Pozyskiwanie i zwolnienie blokady).

Funkcje

edytuj
  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t* mutex)  (doc)
  • int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t* mutex, const struct timespec *abstime)  (doc)

Przykład

edytuj
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>	// sleep

pthread_mutex_t	mutex	= PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t	cond	= PTHREAD_COND_INITIALIZER;

char warunek = 0;

void* watek(void* numer) {
	printf("\turuchomiono wątek #%d\n", (int)numer);
	while (1) {
		pthread_mutex_lock(&mutex);
		do {
			if (warunek)
				break;
			else {
				printf("\twątek #%d oczekuje na sygnał...\n", (int)numer);
				pthread_cond_wait(&cond, &mutex);
				printf("\t... wątek #%d otrzymał sygnał!\n", (int)numer);
			}
		} while (1);
		pthread_mutex_unlock(&mutex);
		/* ... */
	}

	return NULL;
}

#define N 5	/* liczba wątków */

int main() {
	pthread_t id[N];
	int i;

	puts("początek programu");

	/* utworzenie wątków */
	for (i=0; i < N; i++) {
		errno = pthread_create(&id[i], NULL, watek, (void*)(i+1));
		if (errno) {
			perror("pthread_create");
			return EXIT_FAILURE;
		}
	}

	/* wysyłanie sygnałów */

	sleep(1);
	puts("pthread_cond_signal - sygnalizacja");
	pthread_cond_signal(&cond);

	sleep(1);
	puts("pthread_cond_broadcast - rozgłaszanie");
	pthread_cond_broadcast(&cond);

	sleep(1);

	/* kończymy proces, bez oglądania się na wątki */
	puts("koniec programu");
	return EXIT_SUCCESS;
}

Przykładowe wyjście:

$ ./przyklad
początek programu
	uruchomiono wątek #5
	wątek #5 oczekuje na sygnał...
	uruchomiono wątek #3
	wątek #3 oczekuje na sygnał...
	uruchomiono wątek #1
	wątek #1 oczekuje na sygnał...
	uruchomiono wątek #4
	wątek #4 oczekuje na sygnał...
	uruchomiono wątek #2
	wątek #2 oczekuje na sygnał...
pthread_cond_signal - sygnalizacja
	... wątek #5 otrzymał sygnał!
	wątek #5 oczekuje na sygnał...
pthread_cond_broadcast - rozgłaszanie
	... wątek #3 otrzymał sygnał!
	wątek #3 oczekuje na sygnał...
	... wątek #1 otrzymał sygnał!
	wątek #1 oczekuje na sygnał...
	... wątek #4 otrzymał sygnał!
	wątek #4 oczekuje na sygnał...
	... wątek #2 otrzymał sygnał!
	wątek #2 oczekuje na sygnał...
	... wątek #5 otrzymał sygnał!
	wątek #5 oczekuje na sygnał...
koniec programu