Wątki : jaka jest różnica ?

0

Napisałem program obrazujący problem producent - konsument wykorzystujący wątki.

//deklaracje globalne
int bufor;
pthread_mutex_t producent_mutex=PTHREAD_MUTEX_INITIALIZER, konsument_mutex=PTHREAD_MUTEX_INITIALIZER;

//fragment main()
int main () {
...
	pthread_mutex_lock(&konsument_mutex);
	pthread_mutex_unlock(&producent_mutex);

	pthread_create(&producent_t, NULL, producent, &x);
	pthread_create(&konsument_t, NULL, konsument, &x);
...
}

void *producent (void *arg){
int x, i, produkt;
	for (i=0;i<=10;i++) {
		produkt=random()%100;
		pthread_mutex_lock(&producent_mutex);
		bufor=produkt; //<-zakomentowac
		printf("Producent : Wkładam produkt %d do bufora .\n", produkt);
		sleep(random()%5);
		//bufor=produkt;   //<-odkomentowac
		printf(" Skonczylem :)\n");
		pthread_mutex_unlock(&konsument_mutex);
	}
}

void *konsument (void *arg){
int x, i, produkt;

	for (i=0;i<=10;i++) {
		pthread_mutex_lock(&konsument_mutex);
		produkt=bufor;	//<-zakomentowac
		printf("Konsument : Konsumuje produkt %d z bufora .\n", produkt);
		sleep(random()%5);
		//produkt=bufor;   //<-odkomentowac
		printf(" Skonczylem :)\n\n");
		pthread_mutex_unlock(&producent_mutex);
	}
}

Zdziwiła mnie mocno jedna rzecz. Program w postaci jak powyzej dziala prawidlowo tzn. watek producent umieszcza produkt w buforze, a gdy skoncz, umozliwia watkowi konsument pobranie wartosci z bufora. Wynik:

Producent : Wkładam produkt 38 do bufora .
 Skonczylem :)
Konsument : Konsumuje produkt 38 z bufora .
 Skonczylem :)

Producent : Wkładam produkt 12 do bufora .
 Skonczylem :)
Konsument : Konsumuje produkt 12 z bufora .
 Skonczylem :)

Gdy zakomentuje sie linie oznaczone przez "<-zakomentuj" i odkomentuje te oznaczone przez "<-odkomentuj" program przestaje dzialac prawidlowo. Wynik:

Producent : Wkładam produkt 32 do bufora .
 Skonczylem :)
Konsument : Konsumuje produkt 1073962240 z bufora .
 Skonczylem :)

Producent : Wkładam produkt 53 do bufora .
 Skonczylem :)
Konsument : Konsumuje produkt 32 z bufora .
 Skonczylem :)

Za każdym razem przypisywanie/odczytywanie danych z bufora jest przed odblokowaniem mutexu dla konsumenta/producenta, dlatego nie rozumiem dlaczego robi to różnicę...
Moze ktos mi wyjasnic dlaczego tak się dzieje?

0

dlatego ze wyswietlanie wartosci zmiennej produkt bedzie wczesniej niz jej zmodyfikowanie:
/*bufor=produkt; <- tutaj by bylo prawidlowo bo jest zmodyfikowane przed wyswietleniem /
/
wyswietlenie wartosci zmiennej produkt /
printf("Producent : Wkładam produkt %d do bufora .\n", produkt);
sleep(random()%5);
/
jak widac tutaj dopiero jest zmodyfikowana wiec ta wartosc zostanie wyswietlona dopiro przy nastepnym przejsciu petli for */
bufor=produkt;
wszystko ;)
hmm i troche dziwnie rozwiazales to blokowanie poprzez mutex'a powinno byc raczej zrealizowane dla konkeretnego watrku a nie tak na przemian bo to jest strasznei mylace mutex jak wiesz mzoe byc przechwycony tylko dla jednego watku w tym samym czasie dodatkowo niepotrzebnie zmienne lokalne stosujesz jak sa one zbedne wiec lepij to bedzie wygladac jak zrobisz tak:

//globalne
pthread_mutex_t wspolny_mutex=PTHREAD_MUTEX_INITIALIZER;
int bufor;

//fragment main()
int main () {
...

        pthread_create(&producent_t, NULL, producent, &x);
        pthread_create(&konsument_t, NULL, konsument, &x);
...
}

void *producent (void *arg){
int i;
        for (i=0;i<=10;i++) {
                pthread_mutex_lock(&wspolny_mutex);
                bufor=random()%100; 
                printf("Producent : Wkładam produkt %d do bufora .\n", bufor);
                sleep(random()%5);
                printf(" Skonczylem :)\n");
                pthread_mutex_unlock(&wspolny_mutex);
        }
}

void *konsument (void *arg){
int i;

        for (i=0;i<=10;i++) {
                pthread_mutex_lock(&wspolny_mutex);
                printf("Konsument : Konsumuje produkt %d z bufora .\n", bufor);
                sleep(random()%5);
                printf(" Skonczylem :)\n\n");
                pthread_mutex_unlock(&wspolny_mutex);
        }
}
0

OK to juz wiem dlazcego zle sie wyswietlalo :) glupi blad [sciana]

Ale jeszcze sie odwolam do Twojej propozycji z jednym mutexem. A co w sytuacji, gdy powiedzmy proces producent roznych przyczyn zamarudzi troche w wykonywaniu swojej funkcji i konsument bedzie pobieral kilka razy ta sama wartosc bufora, lub producent bedzie dzialal duzo szybciej od konsumenta i bedzie wpisywal do bufora kilka wartosci zanim konsument je odczyta...

Moze wystapic tez krytyczny przypadek, ze konsument po starcie programu pierwszy dostanie sie do mutexa i pobierze wartosc bufora, ktorej jeszcze nie ustalil producent.

Chyba nie zaznaczylem wczesniej ze watki musza byc zsynchronizowane w zapisywaniu/odczytywaniu bufora.

0

no to sa zsynchronizowane przeciez jak juz wspomniaem tylko jeden watek moze w danym momencie przechwycic mutexa jezeli drugi watek bedzie chcial w tym samym czasie to zrobic to bedzie zatrzymany w miejscu wywolania lock dopoki watek ktory przechwycil mutexa go nie zwolni odnoszac sie do Twojego przykladu takie sytuacje ktore opisales nie zajda poniewaz Ty uruchamiasz w pierwszej kolejnosci watek producenta a dopiero pozniej konsumenta a wiec watek producenta jako pierwszy przechwyci zawsze mutexa takze kolejnosc zawsze bedzie zachowana:

producent:
1.przechwytuje(blokuje) mutexa jesli mutexa przechwycil konsument czeka w tym miejscu
2.modyfikuje zawartosc bufora
3.zwalnia mutexa

konsument
1.przechwytuje(blokuje) mutexa jesli mutexa przechwycil producent czeka w tym miejscu
2. odczytuje zawartosc bufora
3. zwalnia mutexa

proste

0

Nie zrozumielismy sie chyba...

Jeden mutex nie wyklucza sytuacji, w ktorej konsument lub producent dwa razy z rzedu dziala na buforze, a cos takiego nie moze wystapic. Dlatego uwazam ze jeden mutex jest w tym momencie niewystarczajacy.

0

wodzil slepy kulawego...
ale nie moga! dzialac w tym smaym momencie skoro operacje na miznnej bufor zachodza dopiero po przechwyceniu mutexa jesli producent przechwycil mutexa to konsument go nie prechwyci dopoki producent tego mutexa nie zwolni... Ty mylisz troszke zasade dzialania zawsze ktorys watek przechwyci jako pierwszy skoro jeden zakonczyl dzialanie i zwolnij a drugi czekal w kolejce na to zeby poprzedni watek zwolnij mutexa przechwyci go jako nastepny to jest jak kolejka w ktorej ustawiaja sie kandydaci... a skoro producent jest tworzony jako pierwszy w main on pierwszy przechwyci mutexa i zacznie ta cala procedure a konsument nastepny pozniej znow producent itd patrz diagram ktory podalem wyzej jasno przedstawia jak to bedzie sie zachowywac... wiec nie wiem gdzie Ty masz swoej watpliwosci...

//edit
zreszta Ty robisz dokladne ta sama operacje zapomnialem dodac Tobie tylko ze na 2 mutexach a przejrzysciej jest na jednym po prostu ten 1 jest zbedny i wprowadza niepotrzebne zamieszanie

0

Zauwaz ze producent najpierw blokuje producent_mutex, a po wykonaniu operacji na buforze odblokowuje konsument_mutex, wiec gdy bedzie probowal po raz drugi z rzedu pisac do bufora to nie bedzie mogl tego zrobic. Dopiero jak konsument odczyta wartosc i zwolni mu producent_mutex stanie sie to mozliwe. Z konsumentem jest odwrotnie - wykonujac swoja funkcje najpierw blokuje konsument_mutex a potem odblokowuje producent_mutex. To zapewnia, ze producent nie zapisze w buforze nowej wartosci dopoki konsument jej nie odczyta.

0
mcveat napisał(a)

Zauwaz ze producent najpierw blokuje producent_mutex, a po wykonaniu operacji na buforze odblokowuje konsument_mutex, wiec gdy bedzie probowal po raz drugi z rzedu pisac do bufora to nie bedzie mogl tego zrobic. Dopiero jak konsument odczyta wartosc i zwolni mu producent_mutex stanie sie to mozliwe. Z konsumentem jest odwrotnie - wykonujac swoja funkcje najpierw blokuje konsument_mutex a potem odblokowuje producent_mutex. To zapewnia, ze producent nie zapisze w buforze nowej wartosci dopoki konsument jej nie odczyta.

dla mnie to co napisalem wyzej jest prostsze i wykonuje to samo ale skoro mowisz ze nie to rob po swojemu chcialem wskazac tylko prawidlowe zastosowanie mutexa bo na tym wlasnie polega stosuje sie jeden mutex i on zapewnie prawidlowy dostep do zwartosci wylacznie dla danego watku w danym czasie...

//Faszczu:
//Maker - zacznij ty stosowac znaki interpunkcyjne bo sie mozna pociac jak sie czyta twoje posty.

0

Skorzystaj z CriticalSection. Szybka i łatwa w impementacji.

0
Maker napisał(a)

odnoszac sie do Twojego przykladu takie sytuacje ktore opisales nie zajda poniewaz Ty uruchamiasz w pierwszej kolejnosci watek producenta a dopiero pozniej konsumenta a wiec watek producenta jako pierwszy przechwyci zawsze mutexa

Jesteś aby pewien tego co piszesz? Sprawdzałeś to różnych komputerach / różnych systemach?
Masz to gdzieś w specyfikacji?

To, że jakiś wątek uruchomisz wcześniej nie zagwarantuje, że dostanie on mutex jako pierwszy. Ta decyzja zostaje w gestii schedulera systemu operacyjnego. Na jednym systemie może działać, na innym pewnie nie będzie. Jeśli chcesz mieć kłopoty w nieprzewidzianych momentach to koduj w oparciu o takie "mądrości ludowe". :>

W Twoim sposobie może się zdarzyć, że konsument będzie próbował odczytać niezapisany bufor. Może się też zdarzyć, że producent zapisze bufor dwukrotnie zanim konsument go odczyta.

Sposób mcveat z dwoma mutexami NIE JEST równoważny Twojemu sposobowi. Jest nieco zakręcony, ale POPRAWNY. Tego typu dostęp można też realizować na monitorach (pthread_cond_xxxx). Przykład jest w manualu (man pthread_cond_init).

0

no raczej z tego co ja wiem akurat to watki i procesy o tym samym priorytecie a tak jest w tym wypadku sa uruchamiane/ponawiane w pierwszej kolejnosci te ktore czekaly najdluzej i jestem raczej pewien ze ten watek ktory utworzysz jako pierwszy w danym procesie bedzie startowal jako pierwszy poniewaz jest to wywolanie funkcji tworzacej, kolejny watek jest tworzony z pewnym opoznieniem. Ten ktory zostal wywolany jako pierwszy logiczne ze nie bedzie czekal az laskawie kolejny zostanei utworzony i wystartuje ;] zreszta skad ma niby to wiedziec troche dziwnie przedstawiles swoja hipoteze ;)
i roznych komputerach lal od kiedy o kolejnosci watkow decyduje sprzet?:D myslalem ze o tym decyduje system operacyjny ale widze ze wysnuwasz nowe "madrosci ludowe" ;]

0

Maker, nie kłóć się, tylko najpierw lepiej doucz się teorii systemów operacyjnych. Gdybyś napisał taki kod jak tu nam przedstawiłeś na egzaminie np. z SOP i dał takie tłumaczenie jego poprawności, to zapewne dr. D. przekreśliłby wszystko i wstawił okrągłe, czerwone 0. :[

O kolejności decyduje:

  1. implementacja schedulera systemu operacyjnego
  2. sprzęt
  3. kompilator
  4. humor Twojej teściowej

Odnośnie pierwszego punktu: scheduler nie musi gwarantować sprawiedliwego dostępu wątków do procesora i zwykle tego nie robi (w każdym razie na pewno nie w systemach Windows i Linux/Unix). A nawet jeśli, to w systemch nie-realtime nie masz żadnej gwarancji wykonania iluś instrukcji w danym czasie. Po prostu nie da się tego przewidzieć. Jedna i ta sama instrukcja asma może się wykonywać czasem przez kilka sekund a innym razem przez parę nanosekund.

Drugi punkt prosto wytłumaczyć istnieniem architektur wielordzeniowych / wieloprocesorowych. Uruchom sobie Linuksa na dwurdzeniowym AMD lub Pentium z HT, to zobaczysz jak programy pisane wedle Twojej zasady "pierwszy uruchomiony wątek pierwszy się wykonuje / wątki dostają po równo" się walą. Wątki się będą wykonywały na raz. Na rzeczywisty czas wpływ będą miały algorytmy zaimplementowane w sprzęcie (procesorze / płycie głównej / kontrolerze pamięci).

Trzeci punkt ma najmniejsze znaczenie, ale: różne kompilatory produkują różny kod, który może mieć nieco inną czasową/pamięciową charakterystykę wykonania - to może doprowadzić do tego, że scheduler podejmie inną decyzję odnośnie przydziału czasu.

Czwarty punkt jest tak oczywisty, że nie wymaga tłumaczenia.

0

kombinujecie a moze po prostu ustalic wartosc kiedy bufor jest pelen ?


#define BUFFEMPTY -1
#define STOPTHREADS -2

int bufor;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER, 
//producent_mutex=PTHREAD_MUTEX_INITIALIZER, // a to po co ?
//konsument_mutex=PTHREAD_MUTEX_INITIALIZER;

// jeden mutex dla wszystkich 3 watkow 


int main () {
...
          // !!!!!!!!
          bufor=BUFEMPTY;
          // !!!!!!!!

	//pthread_mutex_lock(&konsument_mutex); // bu 
	//pthread_mutex_unlock(&producent_mutex);

	pthread_t a=pthread_create(&producent_t, NULL, producent, &x);
	pthread_t b=pthread_create(&konsument_t, NULL, konsument, &x);
          int i=10;
          while(i--){ // czekamy 10 s.
            sleep(1);
            printf("%d\n",i); // tu sie moa napisy pomieszac :>
          }

          pthread_mutex_lock(&mutex);
          bufor=STOPTHREADS; // mozna znulowac watki a/synchronicznie, jesli ustawisz takie jego atrybuty, ale lepiej zmusic je aby sie same zakonczyly
          pthread_mutex_unlock(&mutex);

          pthread_join(a,0);
          pthread_join(b,0);
...
}

void *producent (void *arg){
int x, i, produkt;
	while(1){
                     // !!!!!!!!
                     while(bufor!=BUFEMPTY)usleep(10000); // czekamy az klient odbierze produkt
                     if(bufor==STOPTHREADS)return 0;
                     // !!!!!!!!
		produkt=random()%100;
		//pthread_mutex_lock(&producent_mutex);
		pthread_mutex_lock(&mutex);
		bufor=produkt; //<-zakomentowac
		printf("Producent : Wkładam produkt %d do bufora .\n", produkt);
		sleep(random()%5);
		//bufor=produkt;   //<-odkomentowac
		printf(" Skonczylem :)\n");
		pthread_mutex_unlock(&mutex);
 		//pthread_mutex_unlock(&konsument_mutex);
	}
}

void *konsument (void *arg){
int x, i, produkt;

	while(1){
                     // !!!!!!!!
                     while(bufor==BUFEMPTY)usleep(10000); // czekamy az producent cos stworzy
                     if(bufor==STOPTHREADS)return 0;
                     // !!!!!!!!
		pthread_mutex_lock(&mutex);
		//pthread_mutex_lock(&konsument_mutex);
		produkt=bufor;	//<-zakomentowac
		sleep(random()%5);
		//produkt=bufor;   //<-odkomentowac
                     // !!!!!!!!
                     bufor=BUFEMPTY;  // odebrane
                     // !!!!!!!!
		printf("Konsument : Konsumuje produkt %d z bufora .\n", produkt);
		printf(" Skonczylem :)\n\n");
		pthread_mutex_unlock(&mutex);
		//pthread_mutex_unlock(&producent_mutex);
	}
}

po prostu czekasz . klient czeka na produkt, producent na odbior petla swobodnie moze i musi byc poza zablokowanym muteksem. moze poniewaz zmiany stanu bufora sa podczas zablokowanego muteksa, zas musi zeby nie doprowadzic do zakleszczenia watkow.

nakombinowales sie niepotrzebnie . jeden mutex na wszystkie watki wystarczy. jedne przelacznik. jesi zablokowany przez jeden watek, drugi nie dziala i 3 staje

mozesz ewentualnie pobawic sie semaforami, ale w to zadania na moj gust nie wymaga tego mechanizmu.

0

while(bufor==BUFEMPTY)usleep(10000); // czekamy az producent cos stworzy

A fe! Aktywne oczekiwanie! :>

// toc napisalem, zeby semafora uzyl :> tylko ze dla takiego programu to chyba przerost formy nad trescia [mf]

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