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.