Tenho observado a crescente visibilidade das linguagens e recursos de programação funcional por um tempo. Eu olhei para eles e não vi o motivo do apelo.
Então, recentemente, participei da apresentação "Basics of Erlang" de Kevin Smith na Codemash .
Gostei da apresentação e aprendi que muitos dos atributos da programação funcional tornam muito mais fácil evitar problemas de threading / simultaneidade. Eu entendo que a falta de estado e mutabilidade torna impossível para vários threads alterar os mesmos dados, mas Kevin disse (se eu entendi corretamente) toda a comunicação ocorre por meio de mensagens e as mensagens são processadas de forma síncrona (novamente evitando problemas de simultaneidade).
Mas eu li que Erlang é usado em aplicativos altamente escaláveis (a razão pela qual a Ericsson o criou em primeiro lugar). Como pode ser eficiente lidar com milhares de solicitações por segundo se tudo é tratado como uma mensagem processada de forma síncrona? Não é por isso que começamos a nos mover para o processamento assíncrono - para que possamos aproveitar a execução de vários threads de operação ao mesmo tempo e obter escalabilidade? Parece que essa arquitetura, embora mais segura, é um retrocesso em termos de escalabilidade. o que estou perdendo?
Eu entendo que os criadores do Erlang intencionalmente evitaram o suporte a threading para evitar problemas de simultaneidade, mas pensei que o multi-threading era necessário para alcançar escalabilidade.
Como as linguagens de programação funcional podem ser inerentemente thread-safe, mas ainda assim escalar?
fonte
Respostas:
Uma linguagem funcional não depende (em geral) da mutação de uma variável. Por isso, não precisamos proteger o "estado compartilhado" de uma variável, pois o valor é fixo. Isso, por sua vez, evita a maior parte dos obstáculos que as linguagens tradicionais precisam enfrentar para implementar um algoritmo em processadores ou máquinas.
Erlang vai além das linguagens funcionais tradicionais ao assentar em um sistema de passagem de mensagens que permite que tudo opere em um sistema baseado em eventos, onde um pedaço de código só se preocupa em receber mensagens e enviar mensagens, não se preocupando com uma imagem maior.
O que isso significa é que o programador não está (nominalmente) preocupado se a mensagem será tratada em outro processador ou máquina: simplesmente enviar a mensagem é bom o suficiente para que ela continue. Se ele se preocupa com uma resposta, irá esperar por ela como outra mensagem .
O resultado final disso é que cada snippet é independente de todos os outros snippet. Nenhum código compartilhado, nenhum estado compartilhado e todas as interações provenientes de um sistema de mensagens que podem ser distribuídas entre várias peças de hardware (ou não).
Compare isso com um sistema tradicional: temos que colocar mutexes e semáforos em torno de variáveis "protegidas" e execução de código. Temos uma ligação forte em uma chamada de função por meio da pilha (esperando o retorno ocorrer). Tudo isso cria gargalos que são menos problemáticos em um sistema de nada compartilhado como o Erlang.
EDIT: Devo também salientar que Erlang é assíncrono. Você envia sua mensagem e talvez / algum dia outra mensagem chegue de volta. Ou não.
O argumento de Spencer sobre a execução fora de ordem também é importante e bem respondido.
fonte
O sistema de fila de mensagens é legal porque produz efetivamente um efeito "dispare e espere pelo resultado", que é a parte síncrona sobre a qual você está lendo. O que torna isso incrivelmente incrível é que significa que as linhas não precisam ser executadas sequencialmente. Considere o seguinte código:
Considere por um momento que o métodoWithALotOfDiskProcessing () leva cerca de 2 segundos para ser concluído e que o métodoWithALotOfNetworkProcessing () leva cerca de 1 segundo para ser concluído. Em uma linguagem procedural, esse código levaria cerca de 3 segundos para ser executado porque as linhas seriam executadas sequencialmente. Estamos perdendo tempo esperando a conclusão de um método que poderia ser executado simultaneamente com o outro sem competir por um único recurso. Em uma linguagem funcional, as linhas de código não determinam quando o processador as tentará. Uma linguagem funcional tentaria algo como o seguinte:
Quão legal é isso? Prosseguindo com o código e apenas esperando quando necessário, reduzimos o tempo de espera para dois segundos automaticamente! : D Então, sim, embora o código seja síncrono, ele tende a ter um significado diferente do que nas linguagens procedurais.
EDITAR:
Depois de compreender esse conceito em conjunto com a postagem de Godeke, é fácil imaginar como se torna simples tirar proveito de vários processadores, farms de servidores, armazenamentos de dados redundantes e quem sabe o que mais.
fonte
É provável que você esteja misturando síncrono com sequencial .
O corpo de uma função em erlang está sendo processado sequencialmente. Então, o que Spencer disse sobre esse "efeito auto-mágico" não é verdade para erlang. Você poderia modelar esse comportamento com erlang.
Por exemplo, você pode gerar um processo que calcula o número de palavras em uma linha. Como temos várias linhas, geramos um desses processos para cada linha e recebemos as respostas para calcular uma soma a partir dele.
Dessa forma, geramos processos que fazem os cálculos "pesados" (utilizando núcleos adicionais, se disponíveis) e depois coletamos os resultados.
E isso é o que parece, quando executamos isso no shell:
fonte
O principal fator que permite que Erlang seja escalonado está relacionado à simultaneidade.
Um sistema operacional fornece simultaneidade por dois mecanismos:
Os processos não compartilham o estado - um processo não pode travar outro por design.
Threads compartilham estado - um thread pode travar outro por design - esse é o seu problema.
Com Erlang - um processo de sistema operacional é usado pela máquina virtual e a VM fornece simultaneidade para o programa Erlang não usando threads de sistema operacional, mas fornecendo processos Erlang - isto é, Erlang implementa seu próprio timeslicer.
Esses processos Erlang se comunicam enviando mensagens (manipuladas pela VM Erlang, não pelo sistema operacional). Os processos Erlang se dirigem uns aos outros usando um ID de processo (PID) que tem um endereço de três partes
<<N3.N2.N1>>
:Dois processos na mesma VM, em VMs diferentes na mesma máquina ou duas máquinas se comunicam da mesma maneira - seu dimensionamento é, portanto, independente do número de máquinas físicas nas quais você implanta seu aplicativo (na primeira aproximação).
Erlang é threadsafe apenas em um sentido trivial - ele não tem threads. (A linguagem, isto é, a VM SMP / multi-core usa um thread de sistema operacional por núcleo).
fonte
Você pode ter um mal-entendido de como funciona Erlang. O tempo de execução Erlang minimiza a alternância de contexto em uma CPU, mas se houver várias CPUs disponíveis, todas serão usadas para processar mensagens. Você não tem "threads" no sentido de que tem em outros idiomas, mas pode ter muitas mensagens sendo processadas simultaneamente.
fonte
As mensagens Erlang são puramente assíncronas; se você quiser uma resposta síncrona à sua mensagem, precisará codificar explicitamente para isso. O que possivelmente foi dito é que as mensagens em uma caixa de mensagens de processo são processadas sequencialmente. Qualquer mensagem enviada para um processo vai para essa caixa de mensagem de processo, e o processo pega uma mensagem dessa caixa, processa-a e então segue para a próxima, na ordem que achar adequada. Este é um ato muito sequencial e o bloco de recepção faz exatamente isso.
Parece que você misturou síncrono e sequencial, como Chris mencionou.
fonte
Transparência referencial: consulte http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)
fonte
Em uma linguagem puramente funcional, a ordem de avaliação não importa - em um aplicativo de função fn (arg1, .. argn), os n argumentos podem ser avaliados em paralelo. Isso garante um alto nível de paralelismo (automático).
Erlang usa um modelo de processo em que um processo pode ser executado na mesma máquina virtual ou em um processador diferente - não há como saber. Isso só é possível porque as mensagens são copiadas entre processos, não há estado compartilhado (mutável). O paralelismo de multiprocessador vai muito além do multi-threading, uma vez que os threads dependem da memória compartilhada, pode haver apenas 8 threads rodando em paralelo em uma CPU de 8 núcleos, enquanto o multiprocessamento pode escalar para milhares de processos paralelos.
fonte