São duas frases que descrevem a mesma coisa de pontos de vista diferentes (muito pouco). A programação paralela está descrevendo a situação do ponto de vista do hardware - há pelo menos dois processadores (possivelmente dentro de um único pacote físico) trabalhando em um problema em paralelo. A programação simultânea está descrevendo as coisas mais do ponto de vista do software - duas ou mais ações podem acontecer exatamente ao mesmo tempo (simultaneamente).
O problema aqui é que as pessoas estão tentando usar as duas frases para fazer uma distinção clara quando realmente não existe. A realidade é que a linha divisória que eles estão tentando traçar é nebulosa e indistinta há décadas e se torna cada vez mais indistinta ao longo do tempo.
O que eles estão tentando discutir é o fato de que, uma vez, a maioria dos computadores tinha apenas uma única CPU. Quando você executava vários processos (ou threads) naquela CPU única, a CPU estava realmente executando apenas uma instrução de um desses threads por vez. A aparência de simultaneidade era uma ilusão - a CPU alternava entre executar instruções de diferentes threads com rapidez suficiente para que, para a percepção humana (para a qual algo menos de 100 ms parecesse instantâneo), parecia que ele estava fazendo muitas coisas ao mesmo tempo.
O contraste óbvio para isso é um computador com várias CPUs, ou uma CPU com vários núcleos; portanto, a máquina está executando instruções de vários threads e / ou processos exatamente ao mesmo tempo; código executando um não pode / não tem efeito sobre o código executado no outro.
Agora o problema: uma distinção tão limpa quase nunca existiu. Os projetistas de computadores são realmente bastante inteligentes; portanto, eles perceberam há muito tempo que (por exemplo) quando você precisava ler alguns dados de um dispositivo de E / S, como um disco, levava muito tempo (em termos de ciclos da CPU) para terminar. Em vez de deixar a CPU inativa enquanto isso acontecia, eles descobriram várias maneiras de permitir que um processo / encadeamento faça uma solicitação de E / S e permita que o código de outro processo / encadeamento seja executado na CPU enquanto a solicitação de E / S é concluída.
Portanto, muito antes de as CPUs multinúcleo se tornarem a norma, tínhamos operações de vários threads acontecendo paralelamente.
Essa é apenas a ponta do iceberg. Décadas atrás, os computadores começaram a fornecer outro nível de paralelismo também. Novamente, por serem pessoas bastante inteligentes, os projetistas de computadores perceberam que, em muitos casos, eles tinham instruções que não se afetavam, por isso era possível executar mais de uma instrução no mesmo fluxo ao mesmo tempo. Um exemplo inicial que ficou bem conhecido foi o Control Data 6600. Esse (por uma margem bastante ampla) foi o computador mais rápido do mundo quando foi lançado em 1964 - e grande parte da mesma arquitetura básica permanece em uso atualmente. Ele rastreava os recursos utilizados por cada instrução e possuía um conjunto de unidades de execução que executavam instruções assim que os recursos dos quais dependiam eram disponibilizados, muito semelhante ao design dos processadores Intel / AMD mais recentes.
Mas (como costumavam dizer os comerciais) espera - isso não é tudo. Há ainda outro elemento de design para adicionar ainda mais confusão. Receberam vários nomes diferentes (por exemplo, "Hyperthreading", "SMT", "CMP"), mas todos se referem à mesma idéia básica: uma CPU que pode executar vários threads simultaneamente, usando uma combinação de alguns recursos que são independentes para cada encadeamento e alguns recursos que são compartilhados entre os encadeamentos. Em um caso típico, isso é combinado com o paralelismo em nível de instrução descrito acima. Para fazer isso, temos dois (ou mais) conjuntos de registros arquitetônicos. Em seguida, temos um conjunto de unidades de execução que podem executar instruções assim que os recursos necessários estiverem disponíveis.
Então, é claro, chegamos a sistemas modernos com múltiplos núcleos. Aqui as coisas são óbvias, certo? Temos N (algo entre 2 e 256, no momento) núcleos separados, que podem executar instruções ao mesmo tempo; portanto, temos um caso claro de paralelismo real - executar instruções em um processo / thread não ' afeta a execução de instruções em outro.
Bem, mais ou menos. Mesmo aqui, temos alguns recursos independentes (registros, unidades de execução, pelo menos um nível de cache) e alguns recursos compartilhados (geralmente pelo menos o nível mais baixo de cache, e definitivamente os controladores de memória e a largura de banda na memória).
Resumindo: os cenários simples que as pessoas gostam de contrastar entre recursos compartilhados e recursos independentes praticamente nunca acontecem na vida real. Com todos os recursos compartilhados, acabamos com algo como o MS-DOS, onde só podemos executar um programa por vez e precisamos parar de executar um antes que possamos executar o outro. Com recursos completamente independentes, temos N computadores executando o MS-DOS (sem sequer uma rede para conectá-los) sem capacidade de compartilhar nada entre eles (porque, se é que podemos compartilhar um arquivo, bem, isso é um recurso compartilhado, um violação da premissa básica de que nada está sendo compartilhado).
Todo caso interessante envolve alguma combinação de recursos independentes e recursos compartilhados. Todo computador razoavelmente moderno (e muitos que não são nada modernos) tem pelo menos alguma capacidade de executar pelo menos algumas operações independentes simultaneamente e praticamente qualquer coisa mais sofisticada do que o MS-DOS aproveitou isso para pelo menos algum grau.
A divisão agradável e limpa entre "simultâneo" e "paralelo" que as pessoas gostam de desenhar simplesmente não existe, e quase nunca existe. O que as pessoas gostam de classificar como "simultâneo" geralmente ainda envolve pelo menos um e muitas vezes mais tipos diferentes de execução paralela. O que eles gostam de classificar como "paralelo" geralmente envolve o compartilhamento de recursos e (por exemplo) um processo que bloqueia a execução de outro enquanto usa um recurso compartilhado entre os dois.
As pessoas que tentam fazer uma distinção clara entre "paralelo" e "concorrente" estão vivendo uma fantasia de computadores que nunca existiram.