Linguagem de programação moderna com abstrações intuitivas de programação simultânea [fechado]


40

Estou interessado em aprender programação simultânea, focando no nível do aplicativo / usuário (não na programação do sistema). Estou procurando uma linguagem de programação moderna de alto nível que forneça abstrações intuitivas para escrever aplicativos simultâneos. Quero focar em linguagens que aumentam a produtividade e ocultam a complexidade da programação simultânea.

Para dar alguns exemplos, não considero uma boa opção escrever código multithread em C, C ++ ou Java, porque IMHO minha produtividade é reduzida e seu modelo de programação não é intuitivo. Por outro lado, linguagens que aumentam a produtividade e oferecem abstrações mais intuitivas, como Python e o módulo de multiprocessamento, Erlang, Clojure, Scala etc., seriam boas opções.

O que você recomendaria com base em sua experiência e por quê?

EDIT: Obrigado a todos por suas respostas interessantes. É difícil concluir sem realmente tentar, pois existem muitos bons candidatos: Erlang, Clojure, Scala, Groovy e talvez Haskell. Votei a resposta com os argumentos mais convincentes, mas tentarei todos os bons candidatos antes de decidir qual escolher :)


21
To give an example, I don't consider a good option writing multithreaded code in C, C++, or Java. Por quê? On the other hand, Python and the multiprocessing module, Erlang, Clojure, Scala, etc. are some of my options.Novamente porque? Expanda sua pergunta para definir melhor o que você está realmente procurando.
yannis

2
Então, você quer aprender programação paralela com todas as dicas ou deseja ocultar parte de sua complexidade e se concentrar na produtividade?
MaR

@MaR foco na produtividade e ocultar a complexidade :)
sakisk

Observe que muitos conceitos importantes são evitados (alguns podem ser resolvidos) em algumas dessas linguagens e, portanto, C é realmente a melhor linguagem para aprender simultaneidade. (Ou pelo menos me parece; não sei o suficiente sobre todos os idiomas listados). O aumento da produtividade geralmente conflita com o aprendizado abrangente.
user606723

11
@DeadMG A diminuição da produtividade é o problema deles. Não quero me concentrar na sintaxe do idioma em vez do problema. Definitivamente, não quero acabar lutando com impasses. Como um exemplo simples, quero usar instruções simples como begin transaction end transactione tudo o que está dentro deve estar livre de impasse e ter sucesso ou falhar como um todo.
Sakisk

Respostas:


33

Você certamente deve olhar para o Clojure - na minha opinião, é a melhor linguagem moderna para programação multinúcleo e é extremamente produtivo.

Chaves de atributo:

  • É uma linguagem funcional , que é uma vantagem para a simultaneidade e sua capacidade de desenvolver usando abstrações de nível superior. Possui estruturas de dados persistentes totalmente imutáveis ​​e sequências preguiçosas que serão familiares a qualquer pessoa com experiência em linguagens funcionais como Haskell.
  • Possui um sistema de memória transacional de software muito novo para acesso simultâneo sem bloqueios ao estado mutável. Tornar o código seguro para simultaneidade é geralmente tão simples quanto agrupá-lo em um bloco (dosync ....).
  • É um Lisp - que o torna extremamente poderoso para metaprogramação baseada em macro e geração de código. Isso pode trazer vantagens significativas de produtividade (ensaio de Paul Graham - "Beating The Averages")
  • É uma linguagem da JVM - assim, você não apenas obtém acesso à enorme variedade de bibliotecas e ferramentas no ecossistema Java, mas também se beneficia do enorme esforço de engenharia necessário para tornar a JVM uma plataforma eficaz para aplicativos simultâneos do lado do servidor. Para propósitos práticos, isso oferece uma enorme vantagem sobre idiomas que não têm esse tipo de base para construir.
  • É dinâmico - o que resulta em código muito conciso e muita produtividade. Observe, no entanto, que você pode usar dicas de tipo estático opcionais para desempenho, se necessário.
  • A linguagem é projetada em torno de abstrações que são um pouco difíceis de explicar, mas o efeito final é que você obtém um conjunto de recursos relativamente ortogonais que podem ser combinados para resolver seus problemas. Um exemplo seria a abstração de sequência, que permite escrever código que lide com todos os tipos de objetos "sequenciais" (que inclui tudo, desde listas, seqüências de caracteres, matrizes Java, infinitas sequências preguiçosas, linhas sendo lidas de um arquivo etc.)
  • Existe uma grande comunidade - útil, perspicaz, mas o mais importante, muito pragmática - o foco em Clojure geralmente é "fazer as coisas".

Alguns exemplos de mini código com uma inclinação de simultaneidade:

;; define and launch a future to execute do-something in another thread
(def a (future (do-something)))

;; wait for the future to finish and print its return value
(println @a)

;; call two functions protected in a single STM transaction
(dosync
  (function-one)
  (function-two))

Em particular, vale a pena assistir a um ou mais desses vídeos:


21
O objetivo das declarações de tipo estático em linguagens fortemente tipadas não é "melhorar o desempenho onde for necessário", e estou ficando meio enjoado dos advogados do Lisp trotarem esse velho palhaço. As declarações de tipo têm dois propósitos: fornecer certas garantias de correção em tempo de compilação e facilitar a leitura do código, especialmente para alguém que não seja o autor original. O desempenho inerentemente melhor que a digitação estática fornece é apenas um bônus.
Mason Wheeler

8
Ultimamente, tive que trabalhar com o código JavaScript de outro desenvolvedor, e essa é a parte mais dolorosa do processo: sem tipos nos argumentos das funções, tenho que procurar por toda a base de código para descobrir o que eles devem ser e o que eles podem fazer com base em onde são chamados. Isso não seria um problema se o JavaScript tivesse retido o sistema de tipo C, além de sua sintaxe geral.
Mason Wheeler

11
@MasonWheeler: IMHO, se você não consegue descobrir como chamar uma função sem anotações de tipo, é um problema com a documentação (ou a falta dela). Mesmo em linguagens tipadas por pato, tudo geralmente tem que satisfazer algumas restrições de tipo estrutural (por exemplo, deve suportar operações aritméticas, deve ser iterável, deve ser indexável etc.). Os tipos estáticos só ajudariam minimamente porque não dariam muita dica sobre o que a função faz .
dsimcha

2
@Mason Eu nunca disse que não havia outras vantagens nas declarações de tipo estático. Na verdade, eu gosto de declarações de tipo estático exatamente pelas razões que você declara. No entanto, eu também gosto dos ganhos de produtividade da digitação dinâmica. É uma troca. Se você tem um bom conjunto de testes, geralmente acho que isso reduz muitas desvantagens da digitação dinâmica, tanto em termos de garantia de correção quanto de fornecimento de código de exemplo para ajudar os novatos a entender o uso correto. YMMV.
Mikera

11
@dsimcha - a alternativa para projetar em torno de abstrações seria projetar em torno de uma implementação concreta. Por exemplo, a maioria das funções antigas do Lisp funcionava apenas em listas vinculadas armazenadas nas células contras. Você precisava de funções diferentes para diferentes estruturas de dados. No Clojure, a função principal da biblioteca funciona em qualquer coisa seqüencial (como na resposta).
Mikera

27

Você pode tentar D. Ele oferece três modelos. Eu recomendo o primeiro ou o segundo.

  1. std.concurrency . Se você usar este módulo para todas as suas necessidades de simultaneidade, uma combinação do idioma e da biblioteca padrão imporá o isolamento entre os threads. Os encadeamentos se comunicam principalmente por meio da passagem de mensagens, com suporte limitado à memória compartilhada de uma maneira que favorece a "segurança em primeiro lugar" e desabilita as corridas de dados de baixo nível. Infelizmente, a documentação do std.concurrency precisa ser aprimorada, mas o modelo está documentado em um capítulo gratuito do livro de Andrei Alexandrescu, "The D Programming Language".

  2. std.parallelism . Este módulo foi projetado especificamente para paralelismo multicore em vez de simultaneidade de caso geral. ( Simultaneidade e paralelismo não são a mesma coisa, embora a simultaneidade seja necessária para implementar o paralelismo. ) Como todo o ponto do paralelismo é desempenho, std.parallelism não oferece nenhuma garantia de isolamento, porque dificultaria a escrita de código paralelo eficiente. No entanto, ele abstrai muitos detalhes de baixo nível propensos a erros, de modo que é muito difícil estragar tudo se você estiver paralelizando as cargas de trabalho que você verificou manualmente são independentes entre si.

  3. core.thread é um wrapper de baixo nível sobre APIs de encadeamento específicas do SO. Tanto o std.concurrency quanto o std.parallelism o usam sob o capô, mas eu o recomendaria apenas se você estiver escrevendo sua própria biblioteca de simultaneidade ou encontrar algum caso de canto ridículo que não possa ser bem executado no std.parallelism ou std .concorrência. Ninguém deve usar algo tão baixo para o trabalho do dia-a-dia.


Você deve ter mencionado a imutabilidade / pureza, o armazenamento local do encadeamento por padrão e o compartilhado que impõe a mutação em ordem seqüencial. Essas são linguagens que faltam no C / C ++ para escrever código simultâneo.
deadalnix

@deadalnix: Para mim, a maioria desses são detalhes do modelo std.concurrency (como o isolamento é imposto). Eu queria manter este post conciso.
dsimcha

Bem, na verdade não. A concordância requer o suporte da biblioteca E do idioma.
deadalnix

@deadalnix: Certo, mas eles foram implementados em grande parte para apoiar o std.concurrency.
dsimcha

23

Erlang é definitivamente uma ótima opção, mas algo um pouco mais prático pode ser o Go , o novo idioma do Google.

Não é tão longe de outros idiomas comuns, por isso é fácil de obter, se você já conhece outros idiomas 'fáceis'. Muitas pessoas o comparam com Python ou mesmo Lua em termos de quão 'confortável' é programar.


@faif está perguntando sobre o nível do aplicativo / usuário, não sobre a programação simultânea de sistemas. Como Erlang se encaixa nisso?
Quíron

@ Raynos: Depende da comunidade.
Donal Fellows

@DonalFellows à sua direita, acho que minha afirmação era muito estreita #
Raynos 23/11/11

11
@Chiron: Erlang é uma linguagem de programação, é usada para criar aplicativos. Normalmente, aplicativos de multiprocessamento. Eu não sei onde ele se encaixa como 'sistemas concorrentes simultâneos', eu nunca ouvi falar de nenhum sistema operacional escrito em Erlang.
23411 Javier

11
Depois de dar uma rápida olhada no tutorial do Go, quero dizer que IMHO uma linguagem com uma sintaxe do tipo C que usa ponteiros (limitados) definitivamente não é uma linguagem moderna que aumenta a produtividade.
Sakisk 23/11

23

Dê uma olhada na Programação Paralela da Microsoft para .net. É muito intuitivo.

Muitos computadores pessoais e estações de trabalho têm dois ou quatro núcleos (ou seja, CPUs) que permitem que vários threads sejam executados simultaneamente. Espera-se que os computadores em um futuro próximo tenham significativamente mais núcleos. Para tirar proveito do hardware de hoje e de amanhã, você pode paralelizar seu código para distribuir o trabalho entre vários processadores. No passado, a paralelização exigia uma manipulação de baixo nível de threads e bloqueios. O Visual Studio 2010 e o .NET Framework 4 aprimoram o suporte à programação paralela, fornecendo um novo tempo de execução, novos tipos de biblioteca de classes e novas ferramentas de diagnóstico. Esses recursos simplificam o desenvolvimento paralelo para que você possa escrever código paralelo eficiente, de alta granularidade e escalonável em um idioma natural sem precisar trabalhar diretamente com threads ou com o pool de threads. http://i.msdn.microsoft.com/dynimg/IC292903.png


+1 Este é exatamente o que ele está pedindo. Embora, quando ocorrerem problemas, será difícil depurá-los sem a compreensão da simultaneidade em um nível inferior. Sem mencionar, assumir isso como iniciante em C # pode ser ... interessante.
usar o seguinte

@ P.Brian.Mackey - eu concordo. No entanto isso não é incomum, não seria um exagero comparar esta a usar ORMs quando um não compreender totalmente modelo relacional e SQL ...
Otávio Décio

11
Especialmente PLINQ. Embora seja útil apenas para um pequeno subconjunto de tarefas, pode ser muito fácil de usar.
svick

21

Erlang e Scala têm simultaneidade baseada em ator , que eu achei muito intuitiva e fácil de aprender.

O modelo de ator em ciência da computação é um modelo matemático de computação simultânea que trata os "atores" como primitivos universais da computação digital simultânea: em resposta a uma mensagem que recebe, um ator pode tomar decisões locais, criar mais atores, enviar mais mensagens e determine como responder à próxima mensagem recebida ... Ela foi usada como estrutura para um entendimento teórico da computação e como base teórica para várias implementações práticas de sistemas concorrentes.


19

Estou aprendendo sobre Haskell agora e a leitura deste artigo me convenceu de que Haskell é uma boa opção para programação simultânea. Por ser puramente funcional (o sistema de tipos sabe se uma função faz alguma entrada, saída ou leitura / modificação do estado global), pode fazer coisas como a Memória Transacional de Software (resumida muito bem no artigo acima), que se comporta de maneira semelhante às transações nos bancos de dados - você obtém várias coisas legais, como atomicidade, com apenas um pouco de açúcar extra. AFAIK, threads Haskell também são muito leves. Além dessas coisas, o fato de o Haskell ser puramente funcional permite que mesmo tarefas simples sejam executadas em paralelo com pouco mais do que uma única palavra-chave (par). fonte


7

A linguagem GO do Google tem algumas ferramentas interessantes para simultaneidade - isso seria outra coisa divertida de se experimentar. Veja: http://golang.org/doc/effective_go.html#concurrency e leia um pouco para exemplos.

A programação simultânea é um tópico amplo e há espaço apenas para alguns destaques específicos do Go aqui.

A programação simultânea em muitos ambientes é dificultada pelas sutilezas necessárias para implementar o acesso correto às variáveis ​​compartilhadas. O Go incentiva uma abordagem diferente na qual valores compartilhados são transmitidos pelos canais e, de fato, nunca são compartilhados ativamente por segmentos de execução separados. Apenas uma goroutine tem acesso ao valor a qualquer momento. Corridas de dados não podem ocorrer, por design. Para incentivar esse modo de pensar, reduzimos a um slogan:

Não se comunique compartilhando memória; em vez disso, compartilhe memória comunicando-se.

Essa abordagem pode ser levada longe demais. A contagem de referência pode ser melhor realizada colocando um mutex em torno de uma variável inteira, por exemplo. Mas, como uma abordagem de alto nível, o uso de canais para controlar o acesso facilita a gravação de programas claros e corretos.

Uma maneira de pensar sobre esse modelo é considerar um programa típico de thread único executando em uma CPU. Não há necessidade de primitivas de sincronização. Agora execute outra instância; também não precisa de sincronização. Agora deixe esses dois se comunicarem; se a comunicação for o sincronizador, ainda não há necessidade de outra sincronização. Os pipelines Unix, por exemplo, se encaixam perfeitamente neste modelo. Embora a abordagem de concorrência de Go se origine no processo de comunicação de processos (CSP) da Hoare, ela também pode ser vista como uma generalização segura de tipo de pipes Unix ...


6

Na próxima versão, o C # facilita ainda mais o que o diagrama mostra. Existem duas novas palavras-chave Async e Await.

O Async é usado como um modificador de função e diz "esta operação executa seu trabalho em outro thread.

Await é usada dentro de uma função assíncrona, e é aí que a mágica acontece. Basicamente, Await informa ao compilador para executar a operação seguindo a palavra-chave em um thread separado e aguardar os resultados. Qualquer código após a chamada em espera é executado após a operação.

TAMBÉM, a operação é sincronizada com o thread de chamada (por isso, se você estiver executando uma operação assíncrona em resposta a um clique no botão, não precisará postar manualmente novamente no thread da interface do usuário). Duas pequenas palavras-chave e você obtém muito poder de simultaneidade. Leia mais aqui


Observe que qualquer compilador C # OS decente já suporta C # 5, assíncrono e aguarda.
Raynos

Basicamente, Await diz ao compilador para executar a operação seguindo a palavra-chave em um thread separado e aguardar os resultados. Gostaria de saber se esta resposta está correta - async aguardar não é sobre tópicos. Este artigo explica o seguinte: Não há thread
sventevit 6/08/19

Bom ponto. Eu acho que estava falando muito simplesmente sobre isso. O que realmente acontece é que é feita uma "continuação" que assina o evento da tarefa em "aguardar" sendo concluída. E sim, certas operações de E / S e thread.sleep () (que basicamente respondem a uma interrupção do relógio) não possuem um thread. mas e as Tarefas criadas manualmente que não possuem E / S, digamos que fizemos uma calculadora de fibonacci aguardável? Tecnicamente, o artigo está certo "Não há discussão", mas na realidade nunca houve, sempre foi um conceito que usamos para esconder os detalhes do que o sistema operacional estava fazendo por nós.
Michael Brown

6

Eu ainda recomendaria C ++. É mais do que capaz das abstrações necessárias para escrever código simultâneo decente. A probabilidade esmagadora é que você simplesmente tem uma biblioteca ruim para fazer o trabalho, pois boas bibliotecas para fazer o trabalho são relativamente novas e, na verdade, o conhecimento para usar bem o C ++ não é exatamente comum. O TBB da Intel existe há apenas alguns anos e o PPL da Microsoft só é vendido desde o ano passado.

Se você usar algo como TBB ou PPL, o código simultâneo não será exatamente trivial para escrever, na medida em que a simultaneidade nunca seja trivial, mas longe de ser árdua. Se você usa pthreads ou threads do Win32 diretamente, não é de admirar que você não goste - você está praticamente escrevendo no assembler com essas funções. Mas com o PPL, você está falando sobre algoritmos funcionais padrão paralelos para você, estruturas de dados genéricas para acesso simultâneo e esse tipo de coisa boa.


11
Confira Boost.Threads ou C ++ 0x std::thread(ou std::tr1::thread). Na verdade, é uma abstração muito boa, IMO.
greyfade

11
@greyfade: Eles não têm absolutamente nada no PPL ou TBB. boost::threadé apenas um invólucro do SO com um pouco de RAII. PPL e TBB são algoritmos simultâneos reais, recipientes, etc.
DeadMG

6

Um plug para Ada é necessário aqui, pois possui todas as abstrações de nível superior para paralelismo e simultaneidade. também conhecido como tarefa . Também como o OP pediu intuitivo (um critério subjetivo!), Acho que uma abordagem diferente para o mundo centralizado em java pode ser apreciada.


5

Eu sugeriria Groovy / Java / GPars se você puder basear-se na JVM, pois permite atores, fluxo de dados, processos sequenciais de comunicação (CSP), paralelismo de dados, memória transacional de software (STM), agentes, ... O ponto aqui é que existe existem muitos modelos de simultaneidade e paralelismo de alto nível, cada um com diferentes "pontos positivos". Você não deseja usar um modelo que não esteja em harmonia com a solução para um problema que você está tentando construir. Idiomas e estruturas com apenas um modelo o obrigam a invadir algoritmos.

É claro que eu posso ser visto como tendencioso como colaborador do Groovy e GPars. Por outro lado, trabalho com CSP e Python, cf. Python-CSP.

Um ponto adicional é que a pergunta original é sobre aprendizado, não sobre escrever um sistema de produção. Portanto, a combinação Groovy / Java / GPars é uma boa maneira de aprender, mesmo que o trabalho final de produção seja feito em C ++ usando algo como Just :: Thread Pro ou TBB, em vez de ser baseado em JVM.

(Alguns links de URL perfeitamente razoáveis ​​tiveram que ser removidos devido a algum pânico sobre o envio de spam pelo site host.)


Russel, se você quiser, você pode me dizer o que você quer ligados na sala de bate-papo e eu vou adicioná-los para você: chat.stackexchange.com/rooms/21/programmers
Dan McGrath

4

E o Clojure? Você pode usar o Swing, por exemplo, mas aproveitando o recurso de programação simultânea do Clojure? O Clojure possui uma ótima integração com Java.

Além disso, você considerou a estrutura Java 7 Fork / Join ?


2

Você também pode querer consultar o Groovy e a biblioteca GPars . O GPars BTW é um pouco semelhante ao .NET Parallel Extension mencionado em outra resposta, mas a sintaxe flexível do Groovys facilita a leitura em algumas circunstâncias.


0

O Scala foi mencionado várias vezes nas perguntas e nas respostas, mas não vi nenhuma referência ao Akka, que é uma implementação de ator que pode ser usada com o Scala e o Java.


O que há de errado com esta resposta? Nenhuma outra resposta mencionou akka, e o akka implementa uma abstração de alto nível para programação simultânea.
Giorgio

-1

Eu acho que depende do que você está construindo. Aplicativos de desktop ou servidor? Ouvi dizer que (mas não tenho experiência pessoal) o node.js é ótimo para programação simultânea de servidores (tanto em termos de escrita de código quanto de desempenho). Se eu quisesse escrever um novo aplicativo de servidor, provavelmente tentaria isso. Não tenho certeza sobre aplicativos de desktop ... Eu escrevi uma quantidade razoável de coisas em C # e existem algumas ferramentas que escondem bem a complexidade, embora em outros casos você precise lidar com isso de frente.


-1

Posso ser atingido na cabeça por isso, mas você leu o capítulo 7 da TAOUP ? A seção em que estou pensando especificamente é em threads versus processos. Descobri que o conceito de processamento simultâneo faz a maioria das pessoas pensar em threads, mas nunca vi uma instância em que um thread seja mais fácil e rápido de usar do que gerar um processo filho.

Você está desenvolvendo todos os detalhes de como lidar com simultaneidade com os caras inteligentes que construíram seu sistema operacional. Já existem muitos métodos de comunicação e você não precisa se preocupar com o bloqueio de recursos compartilhados. Basicamente, os threads são um hack de eficiência, que se enquadra na regra de otimização. Não otimize se você não testou por necessidade.

Encontre uma boa biblioteca de subprocessos, como enviado para python . Ou você pode simplesmente escrever vários programas separados em C e escrever outro programa "mestre" para usar garfo e cano para gerar e se comunicar com os subprocessos.


3
É o oposto do que o OP deseja explicitamente ... gerar um processo é tão baixo quanto gerar um encadeamento manualmente. O OP está interessado em abstrações de alto nível de simultaneidade.
319 Konrad Rudolph
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.