Java - wątki

0

Jako, iż jest to mój pierwszy post na tym forum witam wszystkich programmersów :)

Piszę program na zaliczenie zajęć z Javy i utknąłem, proszę o pomoc
Mam tablicę wątków którym potrzebuję przekazać m.in. dwa inty, robię to przez konstruktor w petli for dla kazdego wątku. Wyglada to tak:

w[i] = new MyThread(a, b, xp[i], xp[i]+delta, wyn, 1);

w pierwszej iteracji petli for wszystko jest ok. przekazuję xPoczatkowe (xp[i]), xKoncowe (xp[i] + delta) i w debuggerze pokazuje wartosci xp=0, xk = 32. Natomiast podczas kolejnej iteracji zwiekszam xp o 32+1 i probuje przekazac kolejnemu watkowi kolejny zakres t.j. xp = 33 oraz xk = 64. Co się okazuje ? że do drugiego wątku przekazywane są poprawne parametry ALE w pierwszym wątku wartosci xp oraz xk są takie same ! mianowicie zamiast [0 oraz 32] jest [32 oraz 32]. Sadzilem ze typy podstawowe przekazywane sa przez referencję... Dlaczego xp[i] to tablica intów a nie pojedynczy int? bo kombinuję i kombinuję ale dalej nie wychodzi

aha dodam ze jak przekazywalem wartosci "z palca" tj bez petli for czyli:

w[0] = new MyThread(a, b, 0, 32, wyn, 1);
w[1] = new MyThread(a, b, 33, 65, wyn, 1);
w[2] = new MyThread(a, b, 66, 96, wyn, 1);
//.....

to wszystko dzialalo :) z gory dziekuje za pomoc

a tak wyglada calosc:

package watki;
import java.util.Random;
public class Watki {
	private static void wypiszTab(int[][] tab, String napis){
		System.out.println(napis + " :\n");
		for(int i = 0; i < tab.length; i++){
			for(int j = 0; j < tab[i].length; j++)
				System.out.print(tab[i][j] + "\t");
			System.out.println();
		}
		System.out.println("\n\n");
	}
	private static void wypiszTab(int[] tab,  String napis){
		System.out.println(napis + " :\n");
		for(int i: tab)
			System.out.println(i);
		System.out.println("\n\n");
	}
    
	public static void main(String[] args) {
		int rozmiar = 90, ileWatkow = 3, x = 0, delta = (rozmiar/ileWatkow)+1;
		MyThread[] w = new MyThread[ileWatkow];
		Thread[] ww = new Thread[ileWatkow];
		int[][] a = new int[rozmiar][10];
		int[] b = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, wyn = new int[rozmiar];
		int[] xp = new int[ileWatkow] ;
		Random generator = new Random();
		for(int i = 0; i < a.length; i++)
			for(int j = 0; j < a[i].length; j++)
				a[i][j] = (generator.nextInt()%10)+10;

		wypiszTab(a, "Tablica A");
		wypiszTab(b, "Wektor B");
		
		for(int i = 0; i < ileWatkow; i++){
			xp[i] = x;
			w[i] = new MyThread(a, b, xp[i], xp[i]+delta, wyn, 1);
			ww[i] = new Thread(w[i]);
			ww[i].start();
			x+=delta+1;
		}
		wypiszTab(wyn, "Wyniki");
    }
}
class MyThread implements Runnable{
	private int[][] a;
	private int[] b, wyn;
	private int xp, xk, nrWatq;
	public MyThread(int[][] a, int[] b, int xpp, int xkk, int[]wyn, int nrWatq){
		this.a = a;
		this.b = b;
		this.xp = xpp;
		this.xk = xkk;
		this.wyn = wyn;
		this.nrWatq = nrWatq;
	}
	public void run(){
		System.out.println("Watek " + nrWatq + " rozpoczal sie");
		for(;xp < xk && xp < a.length; xp++){
			int s = 0;
			for(int i = 0; i < a[xp].length; i++) {
				s+=a[xp][i]*b[i];
			}
			wyn[xp] = s;
			System.out.println("Watek " + nrWatq + " wyliczyl wlasnie wiersz");
			try {
				Thread.sleep((int)(Math.random()*16));
			} catch(InterruptedException e){}
		}
		System.out.println("Watek " + nrWatq + " zakonczyl sie");
	}
}

0

no właśnie - "typy podstawowe przekazywane sa przez referencję", czyli wartość takiej zmiennej zmieni się w każdym odwołaniu do niej, również w tych w wątku. skopiuj sobie wartość tej zmiennej zanim przekażesz ją do konstruktora wątku.

0

Przejezyczylem sie... myslalem ze przez wartosc :) ale okazuje sie ze jednak przez referencję. Ok zabieram sie do kodzenia...

0

ŁK coś ci się pomyliło, przyczyna że w kolejnej itertacji xp = xk w pierszym wątku jest zupełnie inna - ten wątek się wykonał i on modyfikuje xp

1

Obiekty proste są przekazywane przez wartość

0
rty napisał(a):

Obiekty proste są przekazywane przez wartość

tez tak wlasnie sadzilem

co do tego czy sie wykonal? watpie... specjalnie wstawilem opoznienie. Oto co program drukuje:

Watek 0 rozpoczal sie
Watek 0 wyliczyl wlasnie wiersz
Watek 2 rozpoczal sie
Watek 2 wyliczyl wlasnie wiersz
Watek 1 rozpoczal sie
Watek 1 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 2 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 2 zakonczyl sie
Watek 0 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 0 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 0 zakonczyl sie
Watek 1 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 1 wyliczyl wlasnie wiersz
Watek 1 zakonczyl sie

Jak widac zanim ktorys z watkow sie zakonczy wszystkie watki sa uruchomione
Siedzac nad debuggerem wydedukowalem ze zmienna xp jest zmieniana w 41-wszej linijce (i to zarowno wewnatrz petli jak i w obiekcie wątku) zupelnie jakby na te zmienne wskazywala ta sama referencja !:

x+=delta;

tylko dlaczego!? ja sie pytam :)

cos mi sie jeszcze przypomnialo z tego co czytalem o wątkach. Ponoć wątki posiadają wspólną pamięć, ale to też nie tlumaczy dlaczego zmienne sie zmieniaja, przeciez odwoluję się poprzez this do zmiennej z AKTUALNEGO obiektu... nie rozumiem tego :)

EDIT:
A najśmieszniejsze jest to, że mimo wszystko program wykonuje się poprawnie :)

1

w pierwszym wątku wartosci xp oraz xk są takie same ! mianowicie zamiast [0 oraz 32] jest [32 oraz 32].

mi się wydaje, że nic wątkowi nie jest zmieniane po cichu, ani nikt mu nic przez referencję int'ów nie przesyła... ;)
Tylko sam sobie tak pętlę napisałeś, że Ci się podczas debugowania chyba pomerdało ;) No i niechcący sobie popsułeś numerowanie wątków:

// no jejku jej - to ci wywala przecież wszystkie instrukcje piszące o tym, co się dzieje ;)
new MyThread(a, b, xp[i], xp[i] + delta, wyn, 1);

// numerek, numerek wątka przekazuj ;-)
new MyThread(a, b, xp[i], xp[i] + delta, wyn, i);

No i ta pętla xp oraz xk...

for (; xp < xk && xp < a.length; xp++) {

no to chyba wiadomo, że zmiena xp w tym "pierwszym wątku" mutuje bez przerwy. W drugim też. I w kolejnych tak samo. I to raczej oczywiste, że w każdym wątku koniec konców będą wartości:
xp == xk == nrwatku * (delta+1)
co się zresztą zgadza z twoją obserwacją, że wątek zakończył na etapie [31;31]. No tak - no na takim etapie powinien skończyć. Zmiennych mu nikt nie modyfikował. On sam sobie przecież xp regularnie inkrementuje, aż dojdzie do momentu, że xp==xk ;) Jak chcesz wiedzieć, z jakimi argumentami wątki tworzono, to sobie zrób jakiś licznik ;-) A te przekazane int'y to sobie jako final zadeklaruj, żebyś przez pomyłkę ich sobie sam nie modyfikował ;)

// ...

private final int xp, xk, nrWatq;

// ...

for (int xi=xp; xi < xk && xi < a.length; xi++) {

program zdaje się, że daje dobre wyniki, bo robi to, co chciałeś, żeby robił. Tylko debugowaniem się zagubiłeś ;)

1

Wydaje mi się, czy chcesz zrobić w zasadzie to samo czym zajmuje się Fork/Join od Javy 7?

0

Ranides masz racje :D zapomnialem ze do licznika petli uzylem nieprzepisanej zmiennej xp :D debugowac zacząłem jak mi to w ogole ruszyc nie chcialo a potem to juz tylko brnąłem zeby tylko znalezc ten "blad". Jakież było moje zdziwienie gdy zorientowałem się że "wątki liczą na złych parametrach" a wyniki wychodzą poprawne :D

Dziekuję!
Tak btw to jest to mnozenie macierzy przez wektor ale do celow testowych uzylem wektora jednostkowego :)

0

Jeżeli chcesz bawić się wątkami, to zapomnij o większości założeń, które uważasz za pewniki. Klasyczne debugowanie kodu wielowątkowego nie ma wiele sensu bo samo zatrzymanie wątku likwiduje zależności czasowe (które przy wielowątkowości mogą być kluczowe), a wypisanie danych powoduje niejawną synchronizację. Debugowanie takiego kodu jest możliwe, ale potrzeba przy nim mnóstwo wyobraźni - bo musisz sobie wyobrazić ze szczątkowych lub fałszywych danych co się naprawdę dzieje.

Przy przekazywaniu danych musisz zawsze posługiwać się synchronizacją, albo zmiennymi atomowymi (w tym zmiennymi prostymi do 32-bit) z modyfikatorem volatile. Żeby uniknąć mieszania danymi przez różne procesory - co prowadzi do przetwarzania przypadkowego - dane te powinny być niezmienne, czyli mające takie właściwości jak wartości proste przekazywane (zawsze) przez wartość. Tylko wtedy przekazanie referencji nie różni się od przekazania wartości.
Tablice są najbardziej dalekie od niezmienności - są one zaprojektowane do skrajnej zmienności. Dlatego ich używanie w kodzie współbieżnym, to zły lub bardzo zły pomysł. Dlatego powstały kontenery wyspecjalizowane właśnie do współbieżności. Należy je używać zamiast tablic.
Każdy dodatkowy wątek, to kolejna pula stanów, więc jeżeli wątki dodatkowo nawzajem wpływają na swoje stany, to likwiduje się determinizm aplikacji. Powstaje wtedy bardzo zły kod i w praktyce cała koncepcja projektu upada. Rozwijanie kodu takiej jak Twój powyżej powyżej, prowadzi właśnie do takiego zachowania aplikacji.

Kiedy człowiek uczy się od początku programowania, to też nie uczy się najpierw jak zbudować kompilator, tylko używa wysokopoziomowych klocków (takich jak printf) do zrobienia prostych rzeczy. Podobnie jest we współbieżności. Tutaj klocki wysokopoziomowe są dostępne dopiero od Javy 1.5, (i rozwinięte w 1.6/1.7), dlatego warto zaczynać naukę od nich. Natomiast przejście do tworzenia nowych obiektów Thread i przekazywania danych z użyciem synchronizacji można przyrównać bardziej do rozpoczęcia nauki assemblera niż do pogłębiania umiejętności posługiwania się współbieżnością. Tak jak przy "normalnym" programowaniu assembler jest potrzebny głównie do zrozumienia budowy i niuansów kodu wyższego poziomu (lub do polepszenia wydajności), tak i tutaj podobną rolę pełni klasa Thread (a także metody wait, notify(All) oraz słowo synchronize i klasa Lock).

Krótko podsumowując. Przeglądnij sobie drzewko interfejsów Executors, Future i klasy je implementujące (np. FutureTask), wysokopoziomowe klasy do sterowania: CountDownLatch, CyclicBarier, Phaser, Semaphore, kontenery mające nazwy zaczynające się od "Concurrent" oraz mające w nazwie "Blocking". Na koniec dopiero weź się za klasę ForkJoinPool oraz jej klocki RecursiveTask i RecursiveAction bo istnieje ona głównie dla maksymalizacji wydajności.

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