Thread, wait(), notify() - dwa wątki działające naprzemian

0

Czołem,

dawno nie pisałem w Javie i coś nie mogę poradzić sobie z zakodowaniem takiego scenariusza:

Mamy pewną grę dla 2 graczy. Gracze wykonują ruchy na przemian, ale każdy gracz obsługiwany jest przez oddzielny wątek. Kiedy jeden z graczy (np. A) 'myśli' nad kolejnym ruchem - drugi gracz/wątek (B) czeka aż ten skończy [wait()]. Gdy gracz (A) wykona kolejny ruch informuje o tym drugiego gracza (B) [notify()] i przechodzi w stan czekania...

...innymi słowy:

... -> A myśli nad kolejnym ruchem (B czeka na niego) -> A wykonuje ruch i informuje o tym B -> B myśli nad kolejnym ruchem (A czeka na niego) -> B wykonuje ruch i informuje o tym A -> itd...

Jak można zaimplementować to w możliwie najprostszy sposób (minimalizując ilość obiektów/klas i linii kodu)? Proszę o pomoc, kod mile widziany...

0

Poczytaj o semaforach. Są znacznie lepszym rozwiązaniem niż ręczna synchronizacja wątków.

0

Nie bardzo widzę to rozwiązanie z zastosowaniem semaforów...

Masz na myśli jeden semafor obsługiwany przez 2 graczy? Schemat działania gracza byłby chyba mniej więcej taki:

class Gracz implements Runnable {
   private static String aktualnyGracz = null;   /* static - gracz, który powinien wykonać teraz ruch */
   private String mojaNazwa = null;   /* moja nazwa gracza, np "A" */
   private Semaphore semaphore = null;
   private PoleGry poleGry = null;

   public Gracz(String nazwa, Semaphore semafor, PoleGry poleGry) {
      this.mojaNazwa = nazwa;
      this.semaphore = semafor;
      this.poleGry = poleGry;
   }

   public void run() {   /* Runnable.run() */
      while (true) {

         semaphore.acquire();   /* + try/catch Exception */

         if ( aktualnyGracz.equals(mojaNazwa) ) {
            _znajdź kolejny ruch (na podstawie polaGry)_
            _zmień aktualnegoGracza na przeciwnego (np. "A" -> "B", "B" -> "A")_
         } else {
            _? sleep() ?_   /* wymagane? */
         }

         semaphore.release();
      }
   }
}

int main() {
   PoleGry pole = new PoleGry();   /* czymkolwiek jest :) */
   Semaphore s = new Semaphore(1);
   Gracz graczA = new Gracz("A", s, pole);
   Gracz graczB = new Gracz("B", s, pole);

   (new Thread(graczA)).start();
   (new Thread(graczB)).start();

}

Taka koncepcja wymaga użycia sleep() przy else {}, w przeciwnym razie wykorzystanie procka wzrasta drastycznie (jak to przy while(true)). Można zrobić to bez sleep()? Tak, aby wątek czekał przy wywołaniu semaphore.acquire() DOKŁADNIE tyle czasu ile potrzeba? Chyba źle to implementuję...

Szukam takiego rozwiązania, aby gracz mógł zmienić status w dowolnym momencie na gracza interaktywnego - czyli kolejnego ruchu dokonuje człowiek, a po jego wyborze gracz komputerowy wykonuje ruch automatycznie...

0

A kto zmusza do użycia jednego semafora? Masz dwa semafory. Pierwszy jest otwierany, a drugi zamykany w momencie gdy gracz 1 kończy ruch. Drugi na odwrót.

0

...wydaje mi się, że gdy te semafory obsługują 2 wątki może dojść do zakleszczenia.

Rozwiązałem sprawę z pomocą LockSupport (java.util.concurrent.locks).

	public static class Gracz extends Thread {
		private String nazwa;
		private Thread watekPrzeciwnika;
		private int stopCond = 50;   /* dla zatrzymania wątku po jakimś czasie */

		public Gracz(String nazwa) {
			this.nazwa = nazwa;
		}

		public void setOtherThred(Thread thread) {
			this.watekPrzeciwnika = thread;
		}

		public void run() {   /* Thread.run() */
			while (true) {
				if (stopCond-- < 1) break;   /* zakończ wątek... */

				LockSupport.park();

				/* szukanie kolejnego ruchu - obliczenia */
				try {
					Thread.sleep((new Random()).nextInt(1000));
				} catch (InterruptedException e) {}
				System.out.println(this.nazwa + " at " + stopCond);
				/* koniec obliczeń */
				
				LockSupport.unpark(watekPrzeciwnika);
			}
		}
	}
	public static void main(String[] args) {
		Gracz A = new Gracz("A");
		Gracz B = new Gracz("B");
		A.setOtherThred(B);
		B.setOtherThred(A);
		A.start();
		B.start();

		LockSupport.unpark(A);
		
		try {
			A.join();
			B.join();
		} catch (InterruptedException e) {}
	}

Dziękuję za pomoc. Pozdrawiam.

0
josh_guest napisał(a)

Gracze wykonują ruchy na przemian, ale każdy gracz obsługiwany jest przez oddzielny wątek.

Ja bym zapytał tylko - po co?
Jeżeli wykonują ruchy na przemian i nie są w stanie podjąć decyzji przy niepełnych danych wynikających z ostatniego ruchu przeciwnika, to nie ma to najmniejszego sensu.
Wg tego co podałeś jeden wątek pracowałby zawsze podczas gdy drugi byłby zawsze "zastopowany" (alternatywnie może chodzić w pustej pętli oczekującej, czyli marnotrawiić czas). Takie rozwiązanie powoduje tylko komplikację kodu i nic poza tym. Żadnych zalet.

Dwa wątki mogłyby mieć sens gdyby każdy z nich równolegle obliczał modyfikację strategii wygrywającej, a podczas własnego ruchu jedynie taktykę wynikającą z ostatniego posunięcia. Tyle, że wtedy wait i notify są najprawdopodobniej zupełnie niepotrzebne (informację o wykonanym ruchu prościej rozwiązać komunikatem, np. wywołaniem metody na rzecz obiektu).

0
Olamagato napisał(a)

[..] Dwa wątki mogłyby mieć sens gdyby każdy z nich równolegle obliczał modyfikację strategii wygrywającej, a podczas własnego ruchu jedynie taktykę wynikającą z ostatniego posunięcia. [..]

Słuszna uwaga Olamagato, oczywiście dokładnie taką koncepcję chcę zastosować w projekcie :) Na tą chwilę jednak było potrzebne mi samo podłoże do równoległych obliczeń.
Po wykonaniu ruchu i odblokowaniu wątku przeciwnika (LockSupport.unpark(wątekPrzeciwnika)) metoda będzie dalej pracować agregując pewne dane statystyczne przyśpieszające wykonanie kolejnego posunięcia w grze. Ta agregacja musi być wykonywana 'pobocznie', nie zajmując zbytnio czasu pracy całego programu (by nie przedłużać oczekiwania na wykonanie ruchu). Stąd podejście wątkowe...

Niemniej, szacuneczek dla Twojej czujności [browar]

0

Monitory są bardzo proste:

class Gracz implements Runnable {
   private static volatile String aktualnyGracz = null;  // koniecznie volatile !!!
   private final String mojaNazwa;   /* moja nazwa gracza, np "A" */
   private final PoleGry poleGry;

   public Gracz(String nazwa, PoleGry poleGry) {
      this.mojaNazwa = nazwa;
      this.poleGry = poleGry;
   }

   public void run() {   /* Runnable.run() */
      while (true) {
        synchronized(poleGry){
          while( !aktualnyGracz.equals(mojaNazwa) ) {
               poleGry.wait();
           }   
  
          _znajdź kolejny ruch (na podstawie polaGry)_
          _zmień aktualnegoGracza na przeciwnego (np. "A" -> "B", "B" -> "A")_
          poleGry.notify();
        }
     }
   }
}

int main() {
   PoleGry pole = new PoleGry();   /* czymkolwiek jest :) */
   Gracz graczA = new Gracz("A", pole);
   Gracz graczB = new Gracz("B", pole);

   (new Thread(graczA)).start();
   (new Thread(graczB)).start();

}

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