Diskuze: komunikace klient-server
V předchozím kvízu, Online test znalostí Java, jsme si ověřili nabyté zkušenosti z kurzu.
Otázka je, jaký máš ten zámek. Problém je už podle názvu chyby ve
vláknu. Tuto chybu jsem kdysi taky hledal. Problém vzniká tím, že se o ten
zámek pere několik synchronizačních bloků najednou. Předpokládám, že
chceš docílit toho, že se pro každého klienta uvolní zámek v momentě,
kdy zpracuješ toho, který je na řadě, že?
Jde o to, že musíš to vlákno těmi klienty (respektive ten zámek) i
uvolňovat a zamykat. Jestli to najdu, hodím ti sem kousek kódu, kterým to
vyřešíš (nebo by to snad někdo dokázal napsat z patra). Přiznám se, že
já se synchronizaci snažím co nejvíc obcházet, jak to jen jde. Osobně mě
nejvíc sere ta náhodnost - nemůžeš třeba nikdy zaručit, komu se zámek
předá. Prosttě se předává náhodně někomu, kdo zrovna čeká. A prý v
tom hrají roli i priority vlákna, ale když jsem to zkoušel na soukromých
projektících, tak mi to nefungovalo:) Ale tuším že v tomto případě by ti
nemuselo vadit, v jakém pořadí se budou klienti o vlákno dělit, hlavně aby
ho pak měli všichni.
no:) našel jsem to, ale mám to jako jednu samostatnou ukázkovou třídu z takového přehledu vláken:) (pro zajímavost - když já třeba učím vlákna, tak nejedu standardním stylem, jako většina, ale právě jsem vytyčil několik základních případů, kdy a jak je potřeba využít vlákno. Takže tato třída ti samostatně nepůjde, je to prostě výňatek, ale můžeš z toho vysosat tu práci se zámky
package threads;
import java.awt.Color;
import test.IThread;
import tools.Delay;
/**
* Lze takhle vyuzit pouze pro 2 objekty. Pokud by se podobnym zpusobem delilo o
* zamek vice objektu, tak tento zpusob nezarucuje, komu se bude pesek (zamek)
* predavat, protoze se predava nahodne tomu, kdo stoji ve fronte nehlede na
* prioritu. Vice objektu by slo v samostatnych vlaknech ridit pomoci rucne
* vytvoreneho algoritmu, ktery by zajistoval, aby se zamek dostaval ve spravnem
* poradi ke konrketnimu vlaknu. To by ovsem slo i jinak nez az pouziti zamku.
*
* @author lubor
*/
public class ESynchronizovanaVlaknaSoucasne extends IThread {
private static final Object LOCK = new Object();
public ESynchronizovanaVlaknaSoucasne() {
super(Color.cyan);
}
@Override
public void run() {
int delay = 2;
array.get(8).setVisible(true);
array.get(9).setVisible(true);
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < panel.getHeight() - array.get(8).getHeight(); i++) {
synchronized (LOCK) {
array.get(8).setLocation(array.get(8).getX(), array.get(8).getY() + 1);
LOCK.notify();
try {
LOCK.wait();
} catch (InterruptedException ex) {
}
Delay.wait(delay);
}
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < panel.getHeight() - array.get(9).getHeight(); i++) {
synchronized (LOCK) {
array.get(9).setLocation(array.get(9).getX(), array.get(9).getY() + 1);
LOCK.notify();
try {
LOCK.wait();
} catch (InterruptedException ex) {
}
Delay.wait(delay);
}
}
}
};
t1.start();
t2.start();
}
}
no když se na to koukám, tak to ještě teda trošku okomentuju, abys věděl, na co se můžeš vykašlat a co k čemu je.
- Konstruktor pouze nastavuje barvičku (protože v projektu používám pro ukázku vlákna let balonků a odlišuju je barvičkami:) ), takže není třeba řešit
- Hodně podstatné je, aby zámek byl pokud možno konstantní, aby si třeba každá nová instance nevytvářela svůj vlastní - s tím jsem taky kdysi bojoval
- ty kolekce, tak ty obsahují právě balonky, které mají nějakou počáteční pozici a já jim v každé ukázce vláken pouze nastavuju barvu a tím je vizuálně odděluju, takže taky teď nepodstatné
- třída Delay je můj tool, který pouze obsahuje try catch se statickou metodou Thread.sleep(int); Používám to, protože nesnáším závorky a díky tomu mám celou pauzu na jednom řádku:) takže to je jen moje úprava. Vykonává prostě jen sleep a nic víc.
- Podstatný je tedy hlavně ten lock a že jej musíš vždy probudit (notify) a uspat (wait). A když toto uděláš v cyklu - pro všechny, tak nemusíš mít pochopitelně dvě samostatná vlákna jako já, ale jak píšu v tom úvodu - nezaručíš, že půjdou postupně. Ale to by ti nemuselo v tomto případě vadit;)
+20 Zkušeností
+2,50 Kč
Jenkings:24.1.2018 21:55
Děkuji za podrobné rady. Věřím že se s tím už nějak poperu. Upřímně řečeno jsem se s vlákny dříve moc nesetkal, takže mi to celkem dost pomohlo.
V podstatě jde o to, že musíš mít jeden jednotný zámek. Jak jsem psal,
je dobré jej deklarovat jako konstantu a víš, že si ho žádné vlákno
nezmění.
No a potom si dělej co chceš, ale musíš najít místo, ve kterém chceš,
aby se běh vlákna zastavil a předal se zámek dalšímu synchronizačnímu
bloku (ovšem náhodnému).
Abys to zastavil, tak na ten zámek zavoláš metodu wait() (tu má každý
objekt) a zabalíš do try catche s interrupted výjimkou.
No a ještě před tím, než tento blok zastavíš, tak "probudíš" ten zámek
u někoho jiného. Musí to být v tomto pořadí, protože po wait() už to
vlákno čeká, než jej někdo probudí. No a notifyAll probudí logicky
všechna zastavené synchronizované bloky.
Neználek:25.1.2018 21:51
Tutoriál obsahuje závažnou chybu.
Smyčka while vytíží naplno jádro procesoru.
while(true) {
synchronized(clientBufReaders) {
for(BufferedReader in : clientBufReaders) {
try {
if(in.ready()) {
System.out.println(in.readLine());
} else {
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Doporučoval bych spíše tutoriál od Oracle
https://docs.oracle.com/…tServer.html
Marian Benčat:26.1.2018 13:25
Neni nahodou neco uvnitr toho loopu blokujici? Pak by to opravdu CPU na 100% nevytizilo, protoze by scheduler odebral CPU time v momente, kdy se ceka na nejake IO - treba dokud neprijde neco po siti.
Ale zdrojak jsem nezkoumal. Jen jsem chtel rici, ze infinite loop rozhodne automaticky neznamena, ze je CPU na 100%.
Petr:26.1.2018 15:45
Myslim ze v tom loopu neni blokujici nic. V podstate principem te aplikace je ze ma 2 vlakna v jednom prijima nova spojeni a pridava si je do nejake kolekce a v druhem (hlavnim) vlakne tou kolekci prochazi a zjistuje jestli je potreba zpracovat nejaka data z klienta. Asi by za tim synchronized blokem mel byt nejaky sleep, treba na 100-500ms podle potreby. Cele CPU to nesezere, protoze skoro vsechna CPU maji dnes vice jader, vytizi to jedno jadro na 100%.
Technicky pri architekture klient server existuji dva pristupy jak
implementovat server. Prvni v dnesni dobe rozsirenejsi je nastartovat nove
vlakno pro kazdeho noveho klienta a resit veskerou komunikaci s klientem v ramci
tohoto vlakna. To je popsane v tom oracle tutorialu.
Druhy pristup, ktery je prezentovany v tomto tutorialu, je ten ze jedno vlakno
resi vsechny klienty najednou (pro ty co znaji jazyk C je to podobne jako
pouziti funkce select na socketu).
Oba pristupy maji neco do sebe, da se obecne rici ze startovat nove vlakno se da
pokud predpokladame ze tech klientu budou stovky, max tisice, ale pokud by
aplikace mela resit desitky tisic klientu pres vlakna tak ji to pravdepodobne
polozi, nebo vyznamne zpomali, protoze prepnuti kontextu mezi vlakny neco stoji,
nehlede na zamky, ktere jsou nutne pro synchronizaci dat a extra pamet, kterou
si vlakna rezervuji.
Kdyz mrknete na implementaci nginx nebo apache, tak taky nestartuji vlakno pro
kazdeho noveho klienta, ale resi to vsechno v ramci jednoho nebo vice
vlaken.
Neználek:26.1.2018 21:53
Kód tutoriálu jsem testoval a opravdu vytěžoval jedno jádro na 100%.
Souhlasím, že existují dva přístupy pro práci se sockety:
- blokující mód - řeší se vláknem pro každého klienta (viz Oracle tutoriál)
- neblokující mód - celý server lze řešit jedním vláknem. V Javě k tomu slouží NIO.
Nesouhlasím s tím, že chybu v programu by řešil sleep. Kleslo by sice vytížení procesoru, ale na druhou stranu by úplně zbytečně stoupla doba odezvy serveru.
Petr:27.1.2018 0:54
Na to co ten program ma delat, tzn. vypisuje po radcich co chodi socketem na standardni vystup, ten sleep staci. Pokud bychom se bavili o realne implementaci, kde bych mel seznam takovychto readeru, tak bych tam pridal fixni thread pool, do ktereho bych predhazoval callable instance s klientem, ktery je zrovna aktivni. Pokud by hrozilo, ze klienti budou vytezovat thread prilis dlouho, tak bych tam jeste pridal limit na prenesene data nebo cas a thread bych uvolnil, s tim ze klient by se zaradil na konec fronty klientu kteri cekaji na volny thread. Zaroven bych pouzil asi selectory z nio, ktere jsi zminil.
Zobrazeno 11 zpráv z 11.