Concorrência x Paralelismo
Conceitualmente, o paralelismo é mais fácil de ser entendido. Ele é a capacidade de executar múltiplas tarefas ao mesmo tempo. Neste exato momento, o seu computador está fazendo paralelismo ao utilizar os múltiplos núcleos da CPU. Enquanto o seu navegador está aberto, o Windows (ou o seu celular), por baixo dos panos, está recebendo e processando informações, seja o streaming de áudio do Spotify ou a sincronização de dados que mantém o WhatsApp e o aplicativo de e-mail atualizados.
Já a concorrência é um conceito um pouco mais específico e mais complexo, geralmente associado ao paralelismo.
Eu interpreto concorrência de duas formas:
Disputa por recursos em um sistema, como escrita, leitura ou processamento.
Capacidade de lidar com múltiplas tarefas de forma concorrente, mesmo que elas não sejam executadas exatamente ao mesmo tempo.
Dependendo do contexto, concorrência pode ser confundida com paralelismo e, de certa forma, o Go (Golang) mistura muito bem esses dois conceitos.
Golang
Nem tudo em Go será necessariamente paralelo, mesmo utilizando goroutines. Podemos criar um número finito de threads virtuais (goroutines). Caso o processador não tenha núcleos suficientes para executar todas essas threads simultaneamente, a Go Engine irá distribuir essas múltiplas goroutines entre os núcleos disponíveis.

Nesse cenário, o processamento ocorre de forma concorrente, as goroutines disputam recursos de processamento, e o Go Engine executa pequenas partes de cada goroutine alternadamente. Isso permite que todas avancem ao longo do tempo, mesmo sem estarem realmente sendo executadas em paralelo o tempo todo.
JavaScript
O JavaScript, por ser single-threaded, não permite paralelismo de forma nativa. Porém, ele é altamente concorrente por causa do Event Loop e da Call Stack, conceitos nos quais não irei me aprofundar neste texto, pois estou escrevendo um conteúdo específico sobre Event Loop e Call Stack.

Ainda assim, podemos considerar a existência de um paralelismo indireto em sistemas JavaScript. Aqui, vou desconsiderar chamadas a APIs externas, pois, ao meu ver, isso seria forçar um pouco a definição, já que esse comportamento é relativamente óbvio.
O que quero dizer com paralelismo em JavaScript é quando utilizamos funções que delegam processamento para o navegador ou para o sistema operacional, geralmente expostas pelo BOM (Browser Object Model) . Exemplos disso são temporizadores como setTimeout e setInterval.
No ambiente Node.js, ocorre algo semelhante. Apesar de o JavaScript continuar sendo single-threaded, certas operações são delegadas para threads internas ou diretamente para o sistema operacional, permitindo que o programa continue executando outras tarefas enquanto essas operações são processadas em paralelo fora da main thread.
algumas libs internas do Node.js que utilizam threads: fs, crypto e dns.
const fs = require("fs");
console.log("Início");
fs.readFile("arquivo-grande.txt", "utf-8", (err, data) => {
console.log("Arquivo lido");
});const crypto = require("crypto");
console.log("Início");
crypto.pbkdf2("senha", "salt", 100000, 64, "sha512", () => {
console.log("Hash gerado");
});const dns = require("dns");
dns.lookup("google.com", (err, address) => {
console.log(address);
});Por fim, é possível realizar paralelismo real em JavaScript por meio de Workers. Nesse modelo, o código é executado em threads separadas, cada uma com seu próprio contexto de execução. No entanto, não irei me aprofundar nesse tema aqui, pois pretendo escrever um texto específico sobre Workers.
Resumo
-
Paralelismo: Execução simultânea de múltiplas tarefas de fato ao mesmo tempo, normalmente utilizando múltiplos núcleos de CPU ou múltiplos processadores.
-
Concorrência: Capacidade de estruturar um sistema para lidar com múltiplas tarefas no mesmo intervalo de tempo, podendo ou não executá-las simultaneamente, envolvendo a coordenação de disputas por recursos compartilhados.