Além dos pontos mencionados nas outras respostas (difícil provar que as operações são independentes e os programadores pensam em série), há um terceiro fator que precisa ser considerado: o custo da paralelização.
A verdade é que esse paralelismo de encadeamento tem custos muito significativos associados a ele:
A criação de threads é muito cara: para o Kernel, iniciar um thread é quase o mesmo que iniciar um processo. Não tenho certeza dos custos precisos, mas acredito que seja da ordem de dez microssegundos.
A comunicação de encadeamento por mutexes é cara: geralmente, isso requer uma chamada do sistema de cada lado, possivelmente colocando um encadeamento em suspensão e ativando-o novamente, o que produz latência, além de caches frios e TLBs liberados. Em média, obter e liberar um mutex custa em torno de um microssegundo.
Por enquanto, tudo bem. Por que isso é um problema para o paralelismo implícito? Porque o paralelismo implícito é mais fácil de provar em pequenas escalas. Uma coisa é provar que duas iterações de um loop simples são independentes uma da outra; é totalmente diferente provar que a impressão de algo stdout
e o envio de uma consulta para um banco de dados são independentes um do outro e podem ser executados em paralelo ( o processo do banco de dados pode estar do outro lado do canal!).
Ou seja, o paralelismo implícito que um programa de computador pode provar é provavelmente inexplorável, porque os custos da paralelização são maiores que a vantagem do processamento paralelo. Por outro lado, o paralelismo em grande escala que pode realmente acelerar um aplicativo não é comprovável para um compilador. Pense em quanto trabalho uma CPU pode fazer em um microssegundo. Agora, se a paralelização for mais rápida que o programa serial, o programa paralelo deve ser capaz de manter todas as CPUs ocupadas por vários microssegundos entre duas chamadas mutex. Isso requer um paralelismo realmente granular, o que é quase impossível de provar automaticamente.
Finalmente, nenhuma regra sem uma exceção: a exploração do paralelismo implícito funciona onde não há threads envolvidos, como é o caso da vetorização do código (usando conjuntos de instruções SIMD como AVX, Altivec, etc.). Isso realmente funciona melhor para o paralelismo de pequena escala que é relativamente fácil de provar.