Wątki w Javie mała wątpliwośc

0

Cześć
Mam takie pytanie pisze sobie coś z wątkami i korzystam z Semaphore aby kontrolować dwa wątki. Zastanawiam się nad tym aby drugi wątek, który czeka na wykonanie pierwszego przechodził w stan spoczynku gdy nie ma zasobów i robić to metodą wait(), natomiast w pierwszym wątku po wykonaniu oprócz zwolnienia semafora wywołać notify() i poinformować wątek. Jednak nie wiem czy w tej sytuacji nie będzie to błędne w zadaniu, mam wykorzystać semafory a wait() i notify() kojarzą mi się z monitorami.
Dzięki za podpowiedź.

1

W Javie każdy obiekt jest monitorem, ale nie oznacza to, żeby robić semafor po to, żeby używać go jak zwykłego Objecta, bo to jest bez sensu. Użyj metod release(), acquire() itp dostępnych w Semaphore, ale niedostępnych w Object.

0

ok dzięki. gdzieś jakieś pierdoły czytałem żeby dodać wait() bo tak to marnuje zasoby czy coś takiego.

1

acquire() jest blokujące, jak ty tam jeszcze chcesz dodać wait()?

0

Jeżeli ktoś na każdy problem z wielowątkowością najpierw proponuje Ci wait/notifyAll, to dzisiaj możesz bezpiecznie założyć, że jest niekompetentny. ;)
Faktem jest, że niemal każda klasa sterująca wielowątkowością używa na najniższym poziomie tej pary, podobnie jak faktem jest, że niemal każda klasa kontenera na najniższym poziomie używa tablic. Po to są jednak konstrukcje wyższego poziomu, żeby z każdym problemem nie zjeżdżać do parteru.

0

A czy java.util.concurrent nie korzysta z busy waitow i innych prymitywow zamiast synchronized, wait i notify? Mi sie wydaje ze tam wlasnie wszystko jest napisane bez tego.

1

Przejrzałem kod AbstractQueuedSynchronizer na którym opiera się chyba większość tych nie-tak-niskopoziomowych klas i rzeczywiście jest tam mnóstwo pętli, ale one są moim zdaniem po to, żeby obsłużyć te kolejki wątków. Jest w nim też użycie LockSupport.park i LockSupport.unpark, które to usypiają wątek.

Kodu Object.wait() i Object.notify() nie przejrzałem, bo jest zaszyty w JVM, a nie chce mi się w tym grzebać. Myślę jednak, że i tak są robione jakieś pętle w wait() i notify(), no bo jak wskazuje logika, implementacja tych metod nie może używać ani synchronized(), ani wait(), ani notify() :]

0

Jesli wierzyc temu co sie czyta w necie i ksiazkach - java.util.concurrent nie uzywa wait i notify, ktore to z kolei nie bazuja na busy petlach...

1

Gdyby java.util.concurrent korzystało z busy loop, to np Semaphore.acquire() zjadało by cały rdzeń. A ja tymczasem napisałem sobie taki kod:

        final Semaphore semaphore = new Semaphore(0);
        semaphore.acquire();

Proces jest odpalony już ponad godzinę, a Monitor systemu w Ubuntu pokazuje mi Stan = Uśpiony oraz Czas CPU = 0 sekund.

Java.util.concurrent korzysta z java.util.concurrent.locks.LockSupport i polecam poczytać sobie JavaDoca to tej klasy.

1

Semaphore.acquire wola ostatecznie taka metode:

private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

ktora jak dla mnie wyglada na calkiem calkiem nieskonczona petle z warunkeim wyjscia - jak dla mnie busy wait.

Pierwszy lepszy fragment javadoca z klasy LockSupport:

In this sense {@code park} serves as an optimization of a "busy wait" that does not waste as much time spinning, but must be paired with an {@code unpark} to be effective.

Po polsku - jest optymizacja busy waita.

Powtorze jeszcze raz: java.util.concurrent nie uzywa wait, notify. Dlaczego nie? Bo nie uzywa synchronized. A wait i notify mozna tylko wywolac w bloku synchronized.
Zrobilem grepa po zrodlach java.util.concurrent i synchronized nie jest uzywane w klasach Lock, ReentrantLock, LockSupport czy wielu innych. Jest uzywane tu i tam, np. w ForkJoinTask, ale miejsca mozna policzyc na palcach jednej reki.

1

Taaaa, usypianie wątku za pomocą LockSupport.park() na nieokreślony czas, tzn aż do LockSupport.unpark(), to jest busy-wait. Jaaasne.

JavaDoc z LockSupport:

The park method may also return at any other time, for "no reason", so in general must be invoked within a loop that rechecks conditions upon return.

JavaDoc z LockSupport.park():

Disables the current thread for thread scheduling purposes unless the permit is available.
If the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:

Some other thread invokes unpark with the current thread as the target; or
Some other thread interrupts the current thread; or
The call spuriously (that is, for no reason) returns.

This method does not report which of these caused the method to return. Callers should re-check the conditions which caused the thread to park in the first place. Callers may also determine, for example, the interrupt status of the thread upon return.

Teraz już wiesz dlaczego jest tam pętla, czy jeszcze nie?

0

Gdyby java.util.concurrent korzystało z busy loop, to np Semaphore.acquire() zjadało by cały rdzeń. A ja tymczasem napisałem sobie taki kod

Tylko wtedy gdyby busy wait był kretyńsko zaimplementowany. Normalnie busy wait implementuje się tak, żeby nie zużywał bezczynnie czasu procesora tylko umożliwiał niższej warstwie na przełączenie kontekstu na inny wątek (w Javie yield, na niższych poziomach może to być systemowa funkcja jak SwitchToThread).

To co mućka wkleił to po prostu kolejkowa blokada, jak najbardziej używa busy wait'a.

1

Busy wait to dokładnie coś takiego:

while (!warunekSpełniony()) {
  // nic nie rób
}

Nie ma tu, żadnego spania czy przełączania wątków.

W metodzie doAcquireInterruptibly jest wywołanie shouldParkAfterFailedAcquire(p, node), które albo pozwala na parkowanie albo zmienia node.predecessor() na jakiegoś kolejnego poprzednika, a więc cała pętla po prostu przechodzi po liście wątków, które czekają na danym AbstractQueuedSynchronizer.

0

while (!warunekSpełniony()) {

To jest bardzo niefektywny wariant busy waita, którego nie spotkasz w żadnym sensownym programie. Poczytaj sobie jak się implementuje spinlocki, których sednem jest właśnie busy-wait.

1

To podaj jakieś info. Wiki podaje, że busy-wait to dokładnie coś takiego jak napisałem. Reszta to "alternatives".

Z ciekawości odpaliłem taki kod:

public class Main {

    public static void main(String[] args) throws Exception {
        while (true) {
            Thread.yield();
        }
    }
}

Zjada cały rdzeń. Tak więc paplanina o Thread.yield() i podobnych nie ma sensu. Thread.sleep() nie ma w ogóle w AbstractQueuedSynchronizer, a więc cała optymalizacja opiera się na LockSupport.park i LockSupport.unpark, które to są formą binarnego semafora, z tym, że LockSupport.park może wrócić bez żadnego powodu (spuriously) i dlatego trzeba to odpalać w nieskończonej pętli.

PS:
Taki kod również zjada cały rdzeń:

int main(int argc, char **argv) {
    while (true) {
        sched_yield();
    }
}
1

Może sobie ustalcie, co to jest busy-wait, bo wychodzi mi na to, że praktycznie wszystko wam się pod jego definicję podpina ;)
U mnie na wsi busy-wait to konstrukcja:

while (condition) {
 // do nothing
}

busy-wait ze sleep'em to już w zasadzie nie jest busy-wait, tylko polling (albo niech będzie busy-wait-polling)

while (condition) {
 sleep(time)
}

a jeśli wątek czeka na sygnał, korzystając z jakiegoś mutex-opodobnego mechanizmu, to już ni bata nie jest busy-wait. park/unpark różni się od wait/notify semantyką oferowaną przez API. Robią to samo, są zaimplementowane tak samo, ale pozwalają spojrzeć na synchronizację z innej strony:

  • park/unpark - wali mnie, jakiego obiektu nie wolno zmieniać, ten wątek ma mieć blokadę
  • wait/notify - wali mnie, jaki wątek mi tu będzie próbował grzebać, niech nikt nie rusza tego obiektu

Skąd ta pewność, że są "zaimplementowane tak samo", bardzo nisko-poziomowo, i odwołują się bezpośrednio do prymitywów synchronizujących systemu operacyjnego? Bo obie metody mają tę samą "chorobę": spurious wakeup. I dlatego zawsze, w każdym kodzie wielowątkowym, czekając na sygnał musi być pętla. To nie jest żaden busy-wait, tylko normalne zabezpieczenie się przed przypadkowym wybudzeniem wątku, które nastąpić nie powinno. Ten while jest tylko po to, żeby zrealizować następujące zachowanie:

czekaj na sygnał
o jej, system mnie obudził, mimo, że sygnału nie ma?
to czekaj dalej na sygnał

Jak ktoś gdzieś znajdzie kod, który używa prymitywów systemu operacyjnego, a nie używa pętli, to nie znaczy, że znalazł właśnie optymalne nie-busy-wait-owe rozwiązanie. To znaczy, że znalazł rozwiązanie, które jest złe.

1

Ranides:
Bardzo dobrze opisane.

Dla jasności dodam jeszcze wycinek z JavaDoca Object.wait(timeout):

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait(timeout);
     ... // Perform action appropriate to condition
 }

(For more information on this topic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).

Podobnie metoda Object.wait():

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:

 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait();
     ... // Perform action appropriate to condition
 }
0
0x200x20 napisał(a):

while (!warunekSpełniony()) {

To jest bardzo niefektywny wariant busy waita, którego nie spotkasz w żadnym sensownym programie. Poczytaj sobie jak się implementuje spinlocki, których sednem jest właśnie busy-wait.

A to wiesz, jak masz jakiś materiał, to w chęcią chyba każdy by rzucił okiem. Co prawda jest to duży offtopic, i nic do tematu nie ma, ale z ciekawości by sobie człowiek spróbował ogarnąć, żeby wiedzieć, jak to tam sobie systemy operacyjne różne rzeczy realizują.

0

Zjada cały rdzeń

LOL. A czego się niby spodziewałeś jak masz jeden wątek który nawala yieldem i kilka nieobciążających procesor aplikacji? yield nie zmusza schedulera do przełączenia kontekstu, tylko sygnalizuje schedulerowi, że jeśli ma jakieś inne watki do wykonania to może go wykonać. Wzrost wydajności używając yield zauważysz dopiero gdybyś miał dużo wątków mocno obciążających procesor.
Przykładowo SwitchToThread na windowsie zwraca niezerową wartość jeżeli system przełączył kontekst. Taki program:

#include<Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
	int count = 0;
	int yield_count = 0;
	int no_yield_count = 0;
	while(count++ < 10000000) 
	{
		if(SwitchToThread()) yield_count++;
		else no_yield_count++;
	}
	printf("%d %d", yield_count, no_yield_count);
	return 0;
}

Wypisuje: 233 999976 przy nie wielkim obciążeniu procesu co oznacza, że na 10000000 przypadków SwitchToThread odebrał procesor programowi 233 razy.
Statystyka ta mocno się zmienia jeżeli system jest obciążony.

A to wiesz, jak masz jakiś materiał, to w chęcią chyba każdy by rzucił okiem. Co prawda jest to duży offtopic, i nic do tematu nie ma, ale z ciekawości by sobie człowiek spróbował ogarnąć, żeby wiedzieć, jak to tam sobie systemy operacyjne różne rzeczy realizują.

@Ranides - np tutaj http://pages.cs.wisc.edu/~remzi/OSTEP/threads-locks.pdf
Masz ładnie opisane jak są zaimplementowane futexy na linuksie.

1

No dobra człowieku. Podaj mi w takim razie kod implementujący ten twój "zoptymalizowany busy-wait" nie używający Thread.sleep(), ani Object.wait() i LockSupport nawet w wywołaniach pod spodem.

0

No dobra człowieku. Podaj mi w takim razie kod implementujący ten twój "zoptymalizowany busy-wait" nie używający Thread.sleep(), ani Object.wait() i LockSupport nawet w wywołaniach pod spodem.

A niby czemu mam nie używać tych trzech [rotfl] Właśnie tego można użyć do implementowania busy waitów. Mućka ci już cytował:

In this sense {@code park} serves as an optimization of a "busy wait" that does not waste as much time spinning, but must be paired with an {@code unpark} to be effective.

Tak więc jak chcesz żyć w swoim świecie w którym na drzewo mówisz kamień, a na kamień drzewo to prosze bardzo ale nie wyśmiewaj ludzi jak mućka, którzy jak najbardziej używają poprawnej terminologii.

1

No właśnie to jest jakaś lewa-terminologia, bo wszystko Ci w niej się podpina pod busy-wait. Co to w takim razie jest czekanie nie-busy-wait w tym "twoim świecie" ??? o_O

1

0x200x20:
Czy w takim razie:

synchronized (obj) {
         while (<condition does not hold>)
             obj.wait();
         ... // Perform action appropriate to condition
     }

To też busy-wait? Bo tu też jest nieskończona pętla przecież.

Object.wait() powinno używać się w nieskończonej pętli, LockSupport.park() tak samo. Powiedz mi jaka jest różnica pomiędzy busy-wait, a nie-busy-wait.

0

Chociażby taka jaka jest w źródłach Javy java.util.concurrent? Z resztą to jest tylko terminologia ja nie przywiązuję do tego wielkiej wagi. Ale jak widze jak mistrz algorytmiczny wibowit wyśmiewa ludzi tylko dlatego że używają innej terminologii niż no to sorry...
@down: tak jest metaforą, epitetem i oksymoronem ...

1

Znalazłem jedno zdanie:

In this sense park serves as an optimization of a "busy wait" that does not waste as much time spinning, but must be paired with an unpark to be effective.

Nigdzie nie jest napisane, że coś jest busy-loopem, zresztą samo busy wait jest napisane w cudzysłowach, co sugeruje metaforę, a nie dokładną terminologię.
Za to w tej samej klasie, tzn LockSupport, jest napisane, że jest formą semafora.

1

No dobra, czyli suma summarum mamy takie opcje:
Wszystko jest busy-loop-em. Ponieważ busy-loop nie powinien być używany w praktycznie żadnym programie działającym w user-mode, no to javy nie można używać. Ponieważ park/unpark to native funkcje, używające prymitywów oferowanych przez system operacyjny, to wychodzi, że systemowe prymitywy to też busy loopy. Zagadką pozostaje, dlaczego nie mam obciążenia 100% na procesorze. Pewnie monitor systemowy mnie okłamuje.

Myślałem przez chwilę, że to wszystko przez żydów, ale nie. Powoli zaczynam łapać dlaczego tak jest. Bo wibowit jest zły. Wibowit na algorytmach się zna, wiec mu 0x200x20 dowalił pozbawiając go jakichkolwiek sensownych metod zarządzania wątkami. Tak, to wszystko dlatego, że wibowit jest mistrzem algorytmicznym (pomyślał Stirlitz) ;]

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