Zmienna warunkowa / condition variable

0

witam

Chciałbym spytać do czego można wykorzystać zmienne warunkowe z biblioteki pthread - tyczą się jej funkcje pthread_cond_*.
Rozumiem oczywiście, że chodzi o synchronizacje wątków, jednak mając już mutexy - pthread_mutex_lock i pthread_mutex_unlock, gdzie pierwsza z tych funkcji zawsze wykonuje nieaktywne czekanie jeżeli mutex już wcześniej został zatrzaśniety (nie zużywa niepotrzebnie procesora), nie rozumiem do czego potrzebne mi są jeszcze sygnały. Mam tam dwie funkcje: pthread_cond_signal i pthread_cond_wait, zmienna warunkowa zawsze jest powiązana z mutexem. Na początku myślałem ze wprowadzono je dlatego ze gdy czeka sie za pomocą mutexa (pthread_mutex_lock blokuje) to czekanie jest aktywne, ale jeżeli tak nie jest to już kompletnie nie wiem po co mi te zmienne warunkowe.
Mógłby mi ktoś podać przykład wraz z kodem gdzie zmienna warunkowa znajduje swoje zastosowanie? Najlepiej byłoby gdyby był to taki przykład w, ktorym bez zmiennej warunkowej ani rusz... ;-)

0

kodem nie zarzuce, ale mowiac w wielkim skrocie:

wait-on-condition pozwala Ci tak jakby atomowo zatrzasnac dwa zasoby. mutex daje Ci jedynie mozliwosc zatrzasniecia jednego. np. proba zatrzasniecia dwoch mutexow jeden-po-drugim daje pare% szansy ze watki sie przetna jakims pechowym miejscu i jakis inny watek w tym samym czasie zatrzasnie najpierw drugi potem pierwszy mutex.. no i deadlock

condition variable dziala tak, ze:

  • podajesz mu ZATRZASNIETY mutex, ktory CHCESZ MIEC zatrzasniety jednoczesnie zczymstam
  • kiedy kiedystam zechcesz zatrzasnac dodatkowo to-drugie, rozpoczynasz czekanie na zmienna. w tym momencie, system automatycznie ZWALNIA twoj zatrzasniety mutex i rozpoczyna czekanie. to powoduje ze nie blokujesz go niepotrzebnie i inne watki moga go zabrac na czas Twojego czekania na zmienna
  • kiedystam-wkoncu zmienna zostanie zasygnalizowana. Twoj watek sie budzi, a system natychmiast zatrzaskuje ponownie tentwojmutex

dzieki temu, oczekiwales N czasu az jakis warunek zostanie spelniony, ale nie trzymales bezproduktywnie mutexa -- nie ma szansy na deadlock miedzy zmienna a mutexem

i mozna na tym fakcie oprzec cos bardziej skomplikowanego, gwarantujacego brak deadlockow

oczywiscie, korzystajac z conditiona, trzeba pamietac ze mutex jest UWALNIANY. to znaczy, ze ZASOB ktory on CHRONI nalezy (w momencie wejscia w czekanie na zmienna) pozostawic w stanie bezpiecznym, spojnym, etc

ps. ah, i conditiony oczywiscie nie tylko w pthread siedza;)

0

ok dzieki za odpowiedz
dla przecwiczenia, oto kod krotkiego programu:

#include <pthread.h>
#include <stdio.h>
#include <string.h>

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
char buf[5];

void* produce(void *arg)
{
    char ch;
    for (ch='A'; ch<='C'; ch++) {
        memset(buf, ch, 5);
    }
}

void* consume(void *arg)
{
    int i;
    for (i=0;i<3;i++) {        
        printf("%s\n", buf);
    }
}

int main()
{
    pthread_t pr;
    pthread_t cn;

    pthread_setconcurrency(3);

    pthread_create(&pr, 0, produce, 0);
    pthread_create(&cn, 0, consume, 0);
    
    pthread_join(pr, 0);
    pthread_join(cn, 0);
    
    return 0;
}

watek producenta, trzy razy chce zapelnic buf, pierwszy raz znakami AAAAA, drugi raz BBBBB trzeci raz CCCCC, proces konsumenta jednoczesnie odczytuje ta tablice. Zadanie to zsynchronizowac tak te dwa watki aby zaden ciag znakow nie zostal pominiety. Wynik dzialania programu ma byc taki:
AAAAA
BBBBB
CCCCC

Wczoraj od razu to zrobilem wykorzystujac dwa mutexy:

#include <pthread.h>
#include <stdio.h>
#include <string.h>

pthread_mutex_t rm=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t wm=PTHREAD_MUTEX_INITIALIZER;
char buf[5];

void* produce(void *arg)
{
    char ch;
    for (ch='A';ch<='C'; ch++) {
        pthread_mutex_lock(&wm);

        memset(buf, ch, 5);

        pthread_mutex_unlock(&rm);
    }
}

void* consume(void *arg)
{
    int p;
    for (p=0;p<3;p++) {
        pthread_mutex_lock(&rm);

        printf("%s\n", buf);

        pthread_mutex_unlock(&wm);
    }
}

int main()
{
    pthread_t pr;
    pthread_t cn;

    pthread_setconcurrency(3);

    pthread_mutex_lock(&rm);

    pthread_create(&pr, 0, produce, 0);
    pthread_create(&cn, 0, consume, 0);
    
    pthread_join(pr, 0);
    pthread_join(cn, 0);
    
    return 0;
}

caly czas zastanawiam sie jak to zrobic wykorzystujac jeden mutex i zmienna warunku. Da sie to zrobic wykorzystujac rygiel odczytu-zapisu? (pthread_rwlock_*) - wykorzystuje on mutexy i zmienne warunkowe

0

Coś w tym stylu:

void* produce(void *arg)
{
    char ch;
    for (ch='A';ch<='C'; ch++) 
	{
		pthread_mutex_lock(&mutex);
        
		memset(buf, ch, 5);
		
		pthread_cond_signal(&cond);
		pthread_mutex_unlock(&mutex);
    }
}

void* consume(void *arg)
{
	int p;
	
	pthread_mutex_lock(&mutex);
	
	for (p=0;p<3;p++) 
	{
		pthread_cond_wait(&cond,&mutex);
		printf("%s\n", buf);
	}
	
	pthread_mutex_unlock(&mutex);
}
0

oj chyba nie do konca, same deadlock:

[czester21@czester21 ~]$ gcc -o t t.c -lpthread
[czester21@czester21 ~]$ ./t
^C
[czester21@czester21 ~]$ ./t
^C
[czester21@czester21 ~]$ ./t
CCCCC
^C
[czester21@czester21 ~]$ ./t
^C
[czester21@czester21 ~]$ ./t
CCCCC
^C
[czester21@czester21 ~]$ ./t
CCCCC
^C
[czester21@czester21 ~]$ ./t
BBBBB
CCCCC
^C
[czester21@czester21 ~]$ ./t
CCCCC
^C
[czester21@czester21 ~]$ ./t
CCCCC
^C
0

Pokaż funkcję main.

0

tak wyglada caly program z twoimi modyfikacjami

#include <pthread.h>
#include <stdio.h>
#include <string.h>

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
char buf[5];

void* produce(void *arg)
{
    char ch;
    for (ch='A';ch<='C'; ch++)
        {
                pthread_mutex_lock(&mutex);
       
                memset(buf, ch, 5);
               
                pthread_cond_signal(&cond);
                pthread_mutex_unlock(&mutex);
    }
}

void* consume(void *arg)
{
        int p;
       
        pthread_mutex_lock(&mutex);
       
        for (p=0;p<3;p++)
        {
                pthread_cond_wait(&cond,&mutex);
                printf("%s\n", buf);
        }
       
        pthread_mutex_unlock(&mutex);
}

int main()
{
    pthread_t pr;
    pthread_t cn;

    pthread_setconcurrency(3);

    pthread_create(&pr, 0, produce, 0);
    pthread_create(&cn, 0, consume, 0);
   
    pthread_join(pr, 0);
    pthread_join(cn, 0);
   
    return 0;
}
0
char buf[5];
[...]
memset(buf, ch, 5);
[...]
printf("%s\n", buf); //<--- buf nie zawiera poprawnego c-stringa (brak '\0' na końcu) 

Jeżeli chodzi o resztę, problem zapewne leży w tym, że producer nie czeka na consumera. Z kolei consumer na sztywno oczekuje trzech sygnałów od producera. Jeśli ich nie otrzyma będzie deadlock.

Spróbuj może jeszcze tak:

pthread_mutex_lock(&mutex);
pthread_create(&pr, 0, produce, 0);
pthread_create(&cn, 0, consume, 0);
pthread_mutex_unlock(&mutex);

wątki będą lepiej zsynchronizowane.

0

gdyby buf nie zawieral '\0' na koncu to moje rozwiazanie z dwoma mutexami tez by nie chodzilo, oprocz tego brak '\0' na koncu przeszkadza tylko w mingw (windows) i kompilatorach microsoftu, GNU C Compiler radzi sobie jezeli na koncu nie ma '\0'
jezeli nie wierzysz to mozesz dodac po memset() linike:

buf[4]='\0';

program dalej bedzie sie wywalal

jesli chodzi o ostatnia propozycje z dodaniem ryglowania mutexa przed i po stworzeniu watka to to nie pomaga

0

GNU C Compiler radzi sobie jezeli na koncu nie ma '\0'

Co nie zmienia faktu, że to niepoprawna konstrukcja ;-)

PS. odwróć kolejność tworzenia wątków.

0
0x666 napisał(a)

Co nie zmienia faktu, że to niepoprawna konstrukcja ;-)

to prawda

0x666 napisał(a)

PS. odwróć kolejność tworzenia wątków.

nie pomoglo...

0

nie pomoglo...

U mnie pomogło (pod windowsem i jakąś biblioteką pthread). Pierwotna wersja, którą podałem zachowywała się tak jak podejrzewałem - zanim consumer zaczął działać, producer już był po trzech sygnałach.

PS. ta twoja wersja z dwoma mutexami jest... hmm... niepewna.

0
0x666 napisał(a)

U mnie pomogło (pod windowsem i jakąś biblioteką pthread). Pierwotna wersja, którą podałem zachowywała się tak jak podejrzewałem - zanim consumer zaczął działać, producer już był po trzech sygnałach.

mozesz wkleic caly kod rozwiazania? (wiem ze na podstawie twoich wypowiedzi moge go zlozyc, ale dla pewnosci...)
niestety nawet jezeli dziala pod windowse, a pod linuxem juz nie to znaczy ze watki i tak nie sa poprawnie zsynchronizowane - kwestia dzialania planisty systemowego - proponuje wstawic w procesie producenta po memset() wywolanie sleep(2) i w procesie konsumenta po printf() wywolanie sleep(1), w ten sposob zmusisz planiste do przelaczenia sie pomiedzy watkami - jezeli procesy sa dobrze zsynchronizowane to funkcja sleep(), a co za tym idzie wymuszenie przelaczenia nie powinno rozwalic rozwiazania

0x666 napisał(a)

PS. ta twoja wersja z dwoma mutexami jest... hmm... niepewna.

dlaczego?
moim zdaniem nie ma tam zadnej mozliwosci zakleszczenia... ale napisz o co konkretnie chodzi

0

mozesz wkleic caly kod rozwiazania?

Nie ma takiej potrzeby, bo to jest dokładnie ten kod, tyle że z odwróconą kolejnością tworzenia wątków.

nawet jezeli dziala pod windowse, a pod linuxem juz nie to znaczy ze watki i tak nie sa poprawnie zsynchronizowane - kwestia dzialania planisty systemowego - proponuje wstawic w procesie producenta po memset() wywolanie sleep(2) i w procesie konsumenta po printf() wywolanie sleep(1), w ten sposob zmusisz planiste do przelaczenia sie pomiedzy watkami

A ja myślę, że sam sposób jest po prostu zły. W pierwotnej wersji też próbowałem zmusić planistę do przełączenia kontekstu (po stronie producera) i nic, tzn. czasem consumer wyłapywał sygnały, ale nie wszystkie. Tu potrzeba dwustronnej komunikacji, czyli producer wysyła sygnał i czeka na potwierdzenie odbioru. Z kolei consumer czeka na sygnał od producera i potwierdza odbiór. Co ciekawe Twoja wersja z dwoma mutexami realizuje taką dwustronną komunikacje, dlatego działa. Problem w tym, że używasz tych mutexów jak windowsowe eventy - jeden wątek zatrzaskuje muteksa, a inny zdejmuje blokadę. Nie wiem, może za słabo znam specyfikę mutexów z pthread, ale Twój kod nie powinien działać lub działa "przypadkiem" bo:

  • wątek wielokrotnie zatrzaskuje mutexa, ale nigdy nie zwalnia go. W dokumentacji mamy:

(PTHREAD_MUTEX_NORMAL) [...] Attempting to relock the mutex causes deadlock. [...] If the mutex type is PTHREAD_MUTEX_DEFAULT, attempting to recursively lock the mutex results in undefined behaviour.

  • wątek wielokrotnie zwalnia mutexa, w którego posiadanie nie wszedł:

If the mutex type is PTHREAD_MUTEX_NORMAL [...] If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, undefined behaviour results.

If the mutex type is PTHREAD_MUTEX_RECURSIVE [...] If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, an error will be returned.

If the mutex type is PTHREAD_MUTEX_DEFAULT [...] Attempting to unlock the mutex if it was not locked by the calling thread results in undefined behaviour. Attempting to unlock the mutex if it is not locked results in undefined behaviour.

Sprawdzałem jak zrealizowane jest to undefined behaviour w bibliotece pthread, którą ja mam i tam po prostu zerowany jest licznik locków po czym ustawiany jest event na signaled, bez sprawdzania czy wątek jest w posiadaniu mutexa - czyli zwykła realizacja windowsowych eventów.

0

o ktorym kodzie mowisz..? tym ostatnim? nie wyglada mi niepoprawnie, przeciez po to walsnie sa condition variables zeby chwilowo przekazac kontrole nad mutexem komus innemu. porownaj z

http://publib.boulder.ibm.com/iseries/v5r1/ic2924/index.htm?info/apis/users_76.htm

  • mam na mysli poprawnosc uzycia mutex/cond, a nie te petelki..
0

o ktorym kodzie mowisz..? tym ostatnim? nie wyglada mi niepoprawnie,

Wiem, że jest poprawny, ale w złym miejscu zastosowany, dlatego nie działa.

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