Proč je čtení ze Socketu blokjící operace?

anonym

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #30 kdy: 31. 07. 2018, 21:48:31 »
No, asi z trochu jiného pohledu, Windows mi žerou zvesela 1500 threadů průběžně. Podle stackoverflow

https://stackoverflow.com/questions/373098/whats-the-average-requests-per-second-for-a-production-web-application

Wikipedie měla 100-200 requestů za vteřinu na 1 server. To se dá v pohodě utáhnout stylem 1 request/1 thread, na to nepotřebuju vymýšlet Quasary a Javascripty. I bych mohl klidně z poolu mít udělaných na každý request 10 threadů, takže až 2000 threadů, to PC s Linuxem musí úplně v pohodě zvládnout.

Já jsem zkoušel udělat si 15000 spících threadů a systém to nijak nevytěžuje. Dokonce ani není pravda očividně to, že 1 thread = 1 mb v RAM, to ani náhodou neodpovídalo realitě, spíše mi to přijde jako nanejvýš třetinová hodnota.

Takže asi to v praxi nebude s tím bezthreadovým async zase tak horké. Takže let's go na JDBC a Servlety, tohle je jen takový hype bez kterého se svět nezblázní.


anonym

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #31 kdy: 31. 07. 2018, 22:04:35 »
A tady je výsledek měření. Měl jsem Socket server, které každému připojení udělal Thread a nechal ho čekat 100 vteřin. Potom odeslal "OK" a socket i thread ukončil.

Client vytvářel sockety , připojující se na server každých 5ms. Výsledek dole Threads znamená, kolik vlastně chrápalo na serveru paralelně thradů s připojenými sockety.

Jen ještě upozorňuju, že počet vytvořených Threadů je dvojnásobný: jedenkrát za client sockety a jedenkrát za server accepty.

Threads 100 time 1
Threads 200 time 1
Threads 400 time 1
Threads 800 time 1
Threads 1600 time 0
Threads 3200 time 0
Threads 6400 time 0

Výsledek je, že zde není naprosto žádný overhaul kvůli vysokému množství threadů. Ty thready prostě chrápou a basta, a potom začnou každých 5ms odpovídat "OK".

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #32 kdy: 31. 07. 2018, 22:09:42 »
No, asi z trochu jiného pohledu, Windows mi žerou zvesela 1500 threadů průběžně. Podle stackoverflow

https://stackoverflow.com/questions/373098/whats-the-average-requests-per-second-for-a-production-web-application

Wikipedie měla 100-200 requestů za vteřinu na 1 server. To se dá v pohodě utáhnout stylem 1 request/1 thread, na to nepotřebuju vymýšlet Quasary a Javascripty. I bych mohl klidně z poolu mít udělaných na každý request 10 threadů, takže až 2000 threadů, to PC s Linuxem musí úplně v pohodě zvládnout.

Já jsem zkoušel udělat si 15000 spících threadů a systém to nijak nevytěžuje. Dokonce ani není pravda očividně to, že 1 thread = 1 mb v RAM, to ani náhodou neodpovídalo realitě, spíše mi to přijde jako nanejvýš třetinová hodnota.

Takže asi to v praxi nebude s tím bezthreadovým async zase tak horké. Takže let's go na JDBC a Servlety, tohle je jen takový hype bez kterého se svět nezblázní.

bavíme se o socketových spojeních nebo o requestech? 100-200 requestů za vteřinu může být klidně 100 tisíc uživatelů s otevřenou wikipedií. Pokud využíváš websockety jen pro nějakou okrajovou funkcionalitu, jako např. notifikace, tak nevím, proč by to mělo žrát polovinu výkonu a paměti serveru.

Vysvetlitel

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #33 kdy: 31. 07. 2018, 22:10:31 »
Povim ti, proc tak banalni operace a tolik otazek a debat kolem toho. Koukas na vysledek abstraction leaku v unixu. Spatnej design proste. Lidi tady bud obhajujou, ze lip to napsat neslo nebo
 ochcavky jako epoll prezentujou jako nejaky super reseni, pritom je to akorat hozeni rucniku do ringu a mas si to vyresit low level sam.

Podobna ochcavka je predani callbacku. Ztratis tim traceback a udelas si z kodu spagety. To mohli rovnou nechat goto protoze callbacky jsou prakticky totez.

Jiny reseni je vyhodit planovac vlaken operacniho systemu do rite a udelat si ho v programovacim jazyce znova. Viz async knihovny, Go, apod.

Je doufam poznat ze je to akorat sada hacku jeden za druhym.

Problem je v tom ze 'nemuzes' pouzivat libovolne vlaken, protoze jsou tezkopadny. To je ta designova chyba, protoze tezkopadny bejt vubec nemusi. Viz Go a async v ruznych jazycich. Vlakno by mela bejt absrakce na urovni treba funkce. Dokazes si predstavit, ze muzes pouzit treba jenom 10 funkci na program, protoze to tehdy nejaky jelito zprasilo v designu OS a udelalo je tezkopadny, jen aby to bylo o chlup rychlejsi?

Kdyby to nekoho zajimalo tak chyba je v zakladni datovy strukture vlaken - stacku. Mel se pouzit stack stacku aka cactus stack, kdy jednoduse vyhodis kus stacku do padous a nahradis ho jinym kdyz menis vlakna v planovaci. Vysledkem jsou delimited continuations, ale to je mimo scope teto lekce.

Aspon by mohli ve skolach zminit ze design unixu je deravej shit at si lidi nemysli jaky jsou bedny kdyz ho nauci pouzivat.

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #34 kdy: 31. 07. 2018, 22:11:13 »
A tady je výsledek měření. Měl jsem Socket server, které každému připojení udělal Thread a nechal ho čekat 100 vteřin. Potom odeslal "OK" a socket i thread ukončil.

Client vytvářel sockety , připojující se na server každých 5ms. Výsledek dole Threads znamená, kolik vlastně chrápalo na serveru paralelně thradů s připojenými sockety.

Jen ještě upozorňuju, že počet vytvořených Threadů je dvojnásobný: jedenkrát za client sockety a jedenkrát za server accepty.

Threads 100 time 1
Threads 200 time 1
Threads 400 time 1
Threads 800 time 1
Threads 1600 time 0
Threads 3200 time 0
Threads 6400 time 0

Výsledek je, že zde není naprosto žádný overhaul kvůli vysokému množství threadů. Ty thready prostě chrápou a basta, a potom začnou každých 5ms odpovídat "OK".

postni sem kód.


gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #35 kdy: 31. 07. 2018, 22:14:16 »
Podobna ochcavka je predani callbacku.

což nikdo normální dávno nepoužívá.

anonym

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #36 kdy: 31. 07. 2018, 22:20:11 »
Kód: [Vybrat]
@RunWith(JUnit4.class)
public class Play {

    private static final long RESPONSE_DELAY = 100000;
    AtomicLong count = new AtomicLong(0);
    AtomicLong sumTime = new AtomicLong(0);


    class SocketRespond implements Runnable {
        private final Socket socket;

        public SocketRespond(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (OutputStream os = socket.getOutputStream()) {
                OutputStreamWriter w = new OutputStreamWriter(os);
                PrintWriter p = new PrintWriter(w);
                Thread.sleep(RESPONSE_DELAY);
                p.println("OK");
                p.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class ServerListener implements Runnable {
        ServerSocket serverSocket;

        @Override
        public void run() {
            try {
                serverSocket = new ServerSocket(6666);
                out.println("Srv start");
                while (true) {
                    Socket newConnection = serverSocket.accept();
                    Thread t = new Thread(new SocketRespond(newConnection));
                    t.start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void start() throws IOException, InterruptedException, ExecutionException {
        Thread serverThread = new Thread(new ServerListener());
        serverThread.start();

        Thread.sleep(300);

       
        for(int i = 100; i< 10000; i*=2) {
            List<Thread> threads = new ArrayList<>();
            for(int j = 0; j < i; j++) {
                Thread sockTh = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            connect();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                Thread.sleep(5);
                threads.add(sockTh);
                sockTh.start();
            }

            for(Thread t2 : threads) {
                t2.join();
            }

            out.println("Threads " + i + " time " + sumTime.get() / count.get());
            sumTime.set(0);
            count.set(0);
            threads.clear();
        }


    }
   
    public void connect() throws IOException {
        long timer = System.currentTimeMillis();
        Socket s = new Socket("127.0.0.1", 6666);
        BufferedReader b = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str = b.readLine();
        timer = System.currentTimeMillis() - timer;

        Assert.assertEquals(str, "OK");

        count.incrementAndGet();
        sumTime.addAndGet(timer - RESPONSE_DELAY);
        b.close();
        s.close();
    }
}

bejk

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #37 kdy: 01. 08. 2018, 08:58:23 »
Podobna ochcavka je predani callbacku.

což nikdo normální dávno nepoužívá.

signal handlery, javascript

MarSik

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #38 kdy: 01. 08. 2018, 10:27:24 »
Kdyby to nekoho zajimalo tak chyba je v zakladni datovy strukture vlaken - stacku. Mel se pouzit stack stacku aka cactus stack, kdy jednoduse vyhodis kus stacku do padous a nahradis ho jinym kdyz menis vlakna v planovaci. Vysledkem jsou delimited continuations, ale to je mimo scope teto lekce.

Což bude fungovat pro případ, kdy všechna ta lehká vlákna běží na jednom CPU. Jenže dneska máme i mašiny s pár stovkami jader. A pro plný paralelismus je potřeba mít pro každé to vlákno i paměťově nezávislý stack.

Takže ideální stav pro plné vytížení N CPU je naopak N těžkých posix vláken a interní plánovač co rozděluje lehká vlákna ke zpracování těm posixovým.

Aspon by mohli ve skolach zminit ze design unixu je deravej shit at si lidi nemysli jaky jsou bedny kdyz ho nauci pouzivat.

A vy si prosím uvědomte, že ne každý ho používá na desktopu s minimem jader.

MarSik

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #39 kdy: 01. 08. 2018, 10:30:26 »
Podobna ochcavka je predani callbacku.

což nikdo normální dávno nepoužívá.

V browseru si klidně používejte Promises nebo PubSub. Já si to na jednojádrovém mikrokontroleru s 8 kiB RAM dovolit nemůžu. Stejně je to jen syntaktický cukr, který obaluje interně připravenou callback funkci.

backup

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #40 kdy: 01. 08. 2018, 10:31:57 »
Kdyby to nekoho zajimalo tak chyba je v zakladni datovy strukture vlaken - stacku. ..

Aspon by mohli ve skolach zminit ze design unixu je deravej shit ...

v návrhu unixu byla vlákna?

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #41 kdy: 01. 08. 2018, 13:25:43 »
javascript

V browseru si klidně používejte Promises nebo PubSub. Já si to na jednojádrovém mikrokontroleru s 8 kiB RAM dovolit nemůžu. Stejně je to jen syntaktický cukr, který obaluje interně připravenou callback funkci.


javascript má coroutiny. Mikrokontrolery bych do diskuze vlákna vs. non-blocking IO nezatahoval.

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #42 kdy: 01. 08. 2018, 13:29:13 »
Kód: [Vybrat]
@RunWith(JUnit4.class)
public class Play {

    private static final long RESPONSE_DELAY = 100000;
    AtomicLong count = new AtomicLong(0);
    AtomicLong sumTime = new AtomicLong(0);


    class SocketRespond implements Runnable {
        private final Socket socket;

        public SocketRespond(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (OutputStream os = socket.getOutputStream()) {
                OutputStreamWriter w = new OutputStreamWriter(os);
                PrintWriter p = new PrintWriter(w);
                Thread.sleep(RESPONSE_DELAY);
                p.println("OK");
                p.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class ServerListener implements Runnable {
        ServerSocket serverSocket;

        @Override
        public void run() {
            try {
                serverSocket = new ServerSocket(6666);
                out.println("Srv start");
                while (true) {
                    Socket newConnection = serverSocket.accept();
                    Thread t = new Thread(new SocketRespond(newConnection));
                    t.start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void start() throws IOException, InterruptedException, ExecutionException {
        Thread serverThread = new Thread(new ServerListener());
        serverThread.start();

        Thread.sleep(300);

       
        for(int i = 100; i< 10000; i*=2) {
            List<Thread> threads = new ArrayList<>();
            for(int j = 0; j < i; j++) {
                Thread sockTh = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            connect();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                Thread.sleep(5);
                threads.add(sockTh);
                sockTh.start();
            }

            for(Thread t2 : threads) {
                t2.join();
            }

            out.println("Threads " + i + " time " + sumTime.get() / count.get());
            sumTime.set(0);
            count.set(0);
            threads.clear();
        }


    }
   
    public void connect() throws IOException {
        long timer = System.currentTimeMillis();
        Socket s = new Socket("127.0.0.1", 6666);
        BufferedReader b = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str = b.readLine();
        timer = System.currentTimeMillis() - timer;

        Assert.assertEquals(str, "OK");

        count.incrementAndGet();
        sumTime.addAndGet(timer - RESPONSE_DELAY);
        b.close();
        s.close();
    }
}

bez vláken by to bylo o 80% procent kratší a zvládlo by to 100x víc spojení.

MarSik

Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #43 kdy: 01. 08. 2018, 14:07:58 »
bez vláken by to bylo o 80% procent kratší a zvládlo by to 100x víc spojení.

S vlákny taky, jen je potřeba to napsat správně. Dělat a spravovat si v Javě vlákna ručně je nesmysl už hodně let..

Použijte třeba cached thread pool a úlohy do něj posílejte. Nové vlákno se tím vytvoří dle potřeby a stará vlákna se znovupoužijí nebo ukončí podle zátěže.

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool--
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#submit-java.lang.Runnable-

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Proč je čtení ze Socketu blokjící operace?
« Odpověď #44 kdy: 01. 08. 2018, 14:11:37 »
bez vláken by to bylo o 80% procent kratší a zvládlo by to 100x víc spojení.

S vlákny taky, jen je potřeba to napsat správně. Dělat a spravovat si v Javě vlákna ručně je nesmysl už hodně let..

Použijte třeba cached thread pool a úlohy do něj posílejte. Nové vlákno se tím vytvoří dle potřeby a stará vlákna se znovupoužijí nebo ukončí podle zátěže.

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool--
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#submit-java.lang.Runnable-

thread pool je pro dlouhotrvající spojení nesmysl. Potřebujete jedno vlákno na spojení.