Executando verificação de segurança...
2

Obrigado pela resposta! O vídeo me ajudou a compreender melhor. Acho que a confusão é em distinguir entre multithreading e assincronia.

No vídeo, se eu entendi direito, ele menciona que Elixir é multithread e, como você disse, usa green threads. Já o JavaScript não é multithread, mas isso não significa que não seja assíncrono.

No exemplo do vídeo, ele é injusto com o JavaScript, pois utiliza uma função sleep bloqueante, que nao é parte da linguagem,mas uma biblioteca que ele installou, enquanto em Elixir ele usa uma função não bloqueante (de acordo com ele mesmo). Se ele usasse uma função bloqueante em Elixir e forçasse a execução em uma única thread, teria o mesmo problema.

Se esse é o caso e o JavaScript executa código assíncrono, então, desde que a função seja não bloqueante como a do exemplo em Elixir, deveria ser possível reproduzir o mesmo comportamento e realizar 400 chamadas em 4 segundos, correto? Decidi tentar! :)

Abaixo está a minha reprodução do código do vídeo usando a mesma função bloqueante.

var express = require("express");
var router = express.Router();

router.get("/", function (req, res) {
  require("sleep").sleep(1);
  res.json({ message: "Hello World!" });
});

module.exports = router;

function startServer() {
  var app = express();
  app.use(router);
  app.listen(3002);
  console.log("Server started at http://localhost:3002");
}

startServer();

E aqui está o resultado

Javascript Async siege -r 1 -c 10 http://localhost:3002
** SIEGE 4.1.6
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     2.03 secs:      26 bytes ==> GET  /
HTTP/1.1 200     3.03 secs:      26 bytes ==> GET  /
HTTP/1.1 200     4.04 secs:      26 bytes ==> GET  /
HTTP/1.1 200     5.05 secs:      26 bytes ==> GET  /
HTTP/1.1 200     6.05 secs:      26 bytes ==> GET  /
HTTP/1.1 200     7.05 secs:      26 bytes ==> GET  /
HTTP/1.1 200     8.06 secs:      26 bytes ==> GET  /
HTTP/1.1 200     9.06 secs:      26 bytes ==> GET  /
HTTP/1.1 200    10.07 secs:      26 bytes ==> GET  /

Transactions:                     10 hits
Availability:                 100.00 %
Elapsed time:                  10.07 secs
Data transferred:               0.00 MB
Response time:                  5.55 secs
Transaction rate:               0.99 trans/sec
Throughput:                     0.00 MB/sec
Concurrency:                    5.51
Successful transactions:          10
Failed transactions:               0
Longest transaction:           10.07
Shortest transaction:           1.02

Usando uma função bloqueante, consegui reproduzir a mesma lentidão mostrada no vídeo.

Agora, veja o que acontece se eu usar uma função sleep que não bloqueia:

var express = require("express");
var router = express.Router();

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

router.get("/", function (req, res) {
  //   require("sleep").sleep(1);
  sleep(1000).then(() => {
    res.json({ message: "Hello World!" });
  });
});

module.exports = router;

function startServer() {
  var app = express();
  app.use(router);
  app.listen(3002);
  console.log("Server started at http://localhost:3002");
}

startServer();

Resultado:

(base) ➜  Javascript Async siege -r 1 -c 10 http://localhost:3002 
** SIEGE 4.1.6
** Preparing 10 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /

Transactions:                     10 hits
Availability:                 100.00 %
Elapsed time:                   1.02 secs
Data transferred:               0.00 MB
Response time:                  1.01 secs
Transaction rate:               9.80 trans/sec
Throughput:                     0.00 MB/sec
Concurrency:                    9.93
Successful transactions:          10
Failed transactions:               0
Longest transaction:            1.02
Shortest transaction:           1.01

Observe que cada requisição levou cerca de 1 segundo, e o tempo total foi 1.02 segundos.

Posso até reproduzir o teste mais pesado do vídeo, com 2 repetições e 200 usuários paralelos:

(base) ➜  Javascript Async siege -r 2 -c 200 http://localhost:3002
** SIEGE 4.1.6
** Preparing 200 concurrent users for battle.
The server is now under siege...
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.01 secs:      26 bytes ==> GET  /
....
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /
HTTP/1.1 200     1.02 secs:      26 bytes ==> GET  /

Transactions:                    400 hits
Availability:                 100.00 %
Elapsed time:                   2.08 secs
Data transferred:               0.01 MB
Response time:                  1.03 secs
Transaction rate:             192.31 trans/sec
Throughput:                     0.00 MB/sec
Concurrency:                  197.96
Successful transactions:         400
Failed transactions:               0
Longest transaction:            1.06
Shortest transaction:           1.00

Note como as requisições individuais continuam em torno de 1 segundo, e o total é 2.08 segundos, mais rápido que Elixir no vídeo.

Com relação ao primeiro ponto, o JavaScript roda código assíncrono e consegue responder a chamadas em paralelo, mesmo usando uma única thread. No entanto, é importante lembrar que, se o código for escrito de forma síncrona, ele não será executado de maneira assíncrona. Cabe ao desenvolvedor garantir que o código seja escrito para rodar assincronamente.

Quando o JavaScript encontra funções assíncronas por natureza, como operações de I/O, ele retorna a execução para o fluxo principal do código, permitindo que outras instruções continuem sem esperar. Porém, se o código nunca chegar a uma função assíncrona, ele será executado de forma totalmente síncrona. No meu código acima, essa função assíncrona é o setTimeout. Em algumas linguagem como Python, o desenvolvedor tem que fazer isto explicitamente.

JavaScript se destacou justamente por facilitar a execução de funções de I/O, como leitura de arquivos e chamadas de rede de maneira assíncrona, permitindo que o código continue avançando sem esperar pela resposta do servidor remoto.

O que JavaScript não faz é executar código em paralelo. Todo o código roda em uma única thread e em um event loop. Portanto, se houver algo pesado para processar, como uma grande quantidade de dados, o JavaScript ficará limitado pela capacidade dessa única thread.

Me fale se o que escrevi faz sentido. Estou curioso para saber se consegui entender seu ponto e se estamos alinhados na diferença entre assíncrono e paralelo.

Carregando publicação patrocinada...
1

Cristian, macho eu criei alguns exemplos pra te dá sobre greenthread e goroutines e testei iria trazer o exemplo dos resultados quando eu percebi que o meu conhecimento de relacionado a threads estava fraco, pois acabava que não batia quando realizava o teste era muita teoria, e teoria equivocada.

E reconheço a minha confusão, peço desculpas e obrigado por sua paciência em explicar.