Mutexy i zrozumienie blokowania współdzielonej pamięci między wątkami

0

Chciałem się zapytać o pewien fragment kodu z książki https://www.amazon.com/Advanced-Programming-UNIX-Environment-3rd/dp/0321637739. Mianowicie chodzi o alokację struktury/obiektu na stercie, który będzie współdzielony między wątkami.

Fragment kodu z książki:

#define NHASH 29
#define HASH(id) (((unsigned long)id)%NHASH)

struct foo *fh[NHASH];

pthread_mutex_t hashlock = PTHREAD_MUTEX_INITLIALIZER;

struct foo {
	int f_count;
	pthread_mutex_t f_lock;
	int f_id;
	struct foo *f_next; /* protected by hashlock */
};

struct foo *foo_alloc(int id) {
	struct foo *fp;
	int idx;

	if ((fp = malloc(sizeof(struct foo))) != NULL) {
		fp->f_count = 1;
		fp->f_id = id;
		if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
			free(fp);
			return(NULL);
		}
		idx = HASH(id);
		pthread_mutex_lock(&hashlock);
		fp->f_next = fh[idx];
		fh[idx] = fp;
		pthread_mutex_lock(&fp->f_lock);
		pthread_mutex_unlock(&haslock);
		/* continue initialization */
		pthread_mutex_unlock(&fp->f_lock);
	}
	return(fp);
} /* allocate the object */

Fragment kodu zmodyfikowany przeze mnie:

#define NHASH 29
#define HASH(id) (((unsigned long)id)%NHASH)

struct foo *fh[NHASH];

pthread_mutex_t hashlock = PTHREAD_MUTEX_INITLIALIZER;

struct foo {
	int f_count;
	pthread_mutex_t f_lock;
	int f_id;
	struct foo *f_next; /* protected by hashlock */
};

struct foo *foo_alloc(int id) {
	struct foo *fp;
	int idx;

	if ((fp = malloc(sizeof(struct foo))) != NULL) {
		/* ------------POCZATEK MODYFIKACJI ---------------- */
		if (pthread_mutex_init(&fp->f_lock, NULL) != 0) {
			free(fp);
			return(NULL);
		}
		pthread_mutex_lock(&fp->f_lock);
		fp->f_count = 1;
		fp->f_id = id;
		idx = HASH(id);
		pthread_mutex_unlock(&fp->f_lock);
		/* ----------KONIEC MODYFIKACJI------------------*/
		pthread_mutex_lock(&hashlock);
		fp->f_next = fh[idx];
		fh[idx] = fp;
		pthread_mutex_lock(&fp->f_lock);
		pthread_mutex_unlock(&haslock);
		/* continue initialization */
		pthread_mutex_unlock(&fp->f_lock);
	}
	return(fp);
} /* allocate the object */
}

Chodzi o to, że autor książki nie użył mutexu do zablokowania wątku po wykonaniu funkcji malloc i przypisuje wartości zmiennym nowo-zaalokowanego obiektu bez blokowania wątku. Mi się wydaje, że wątek przy inicjalizacji zmiennych powinien być zablokowany dla innych wątków. Mam rację czy jest to błędny tok rozumowania?

3

Aż do linijki 28 oryginalnego kodu fp jest lokalne. Tzn. jest na stercie, ale kontekst użycia tej pamięci pozostaje lokalny dla wątku, nic innego jej przecież nie dotknie. Dopiero w linijce 28 zaczynają sie odwołania do globalnej zmiennej.

1

Według mnie kod autora jest dobry:

przypisuje wartości zmiennym nowo-zaalokowanego obiektu bez blokowania wątku

Nie ma w tym nic złego, bo po samej alokacji obiekt nie jest "opublikowany" - jedynie bieżący wątek ma do niego dostęp, inne nie są świadome jego istnienia. Dopiero po publikacji, czyli wpisaniu wskaźnika do obiektu do globalnej struktury w tym przypadku konieczne jest lokowanie mutexu tego obiektu do dalszej jego inicjalizacji.

0

struct foo *fp; ten wskaźnik, który jest inicjalizowany wewnątrz wątku sprawia, że sama inicjalizacja obiektu jest lokalna dla wątku i inne wątki nie mają do tego obiektu dostępu chociaż znajduje się na stercie.

W tym momencie fh[idx] = fp; następuje przypisanie adresu tego obiektu do globalnej zmiennej, do której już wszystkie wątki mają dostęp, a skoro wszystkie mają dostęp to inicjalizacja obiektów po wykonaniu tej instrukcji musi być zablokowana, żeby nie wystąpił niepożądane komplikacje.

Raczej zrozumiałem. Dzięki Wam

1

Koda autora wygląda poprawnie, ale czy jest dobry to już inna sprawa (nazwy symboli są do d**y, oraz jestem maniakiem bardzo krótkich funkcji).
Twoja porwaka jest bezcelowa, bo dopóki informacja nie trafi do części wspólnej nie ma co synchronizować, bo tylko jeden wątek ma kontakt z daną strukturą. Czyli dopiero gdy modyfikujesz fh potrzebna jest synchronizacja (heap jest synchronizowany niezależnie przez standardową bibliotekę), wcześniej żaden inny wątek nie ma możliwości by pozyskać wskaźnik na nowo utworzona strukturę.

Nie rozumiem też po co autorowi cześć /* continue initialization */. Nie ma sensu wstawiać tak czegokolwiek nowego, bo wtedy ta funkcja narusza single responsibility principle.

1 użytkowników online, w tym zalogowanych: 0, gości: 1