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

Odpowiedz Nowy wątek
2019-05-15 10:13
0

Chciałem się zapytać o pewien fragment kodu z książki https://www.amazon.com/Advanc[...]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?


Pozostało 580 znaków

2019-05-15 10:35
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.

Pozostało 580 znaków

2019-05-15 10:39
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.

Pozostało 580 znaków

2019-05-15 10:50
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


Pozostało 580 znaków

2019-05-15 11:17
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.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 2x, ostatnio: MarekR22, 2019-05-15 11:34
W takich książkach raczej rzadko przejmują się zasadami czystego kodu. Mnie też to drażni ale staram się nie zwracać na to uwagi dopóki interesujące mnie zagadnienia techniczne są dobrze tłumaczone - Shizzer 2019-05-15 11:21
zależy od autora. Ogólnie lepiej uczyć dobrych zasada "od kołyski", żeby weszły w nawyk. - MarekR22 2019-05-15 11:22
Ciekawe, że heap jest synchronizowany przez stdlib to dużo ułatwia. Czyli jeśli obiekt ląduje na heapie ale jego adres nie ląduje w zmiennej globalnej to inne wątki dostępu do niego nie mają bo nie znają jego adresu tak? - Shizzer 2019-05-15 11:29
to jest konieczność. Bo jest dużo kodu bez jednowątkowego, który operuje na heap. Jeśli heap ma być dostępny z wielu wątków musi być synchronizowany. Mechanizm heap jest dość skomplikowany, bo zależnie od wielkości obiektu stosowana jest inna strategia przydzielania pamięci. System potrafi przydzielać pamięć jedynie pełnymi stronami, więc to standardowa biblioteka dzieli sobie to własne struktury danych, które muszą być synchronizowane. - MarekR22 2019-05-15 11:33

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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