Este artigo foi traduzido por máquina.

The Polyglot Programmer

Simultaneidade com canais, domínios e mensagens

Ted Neward

No último artigo, analisamos a linguagem de programação cobra, uma linguagem orientada a objeto livremente derivada do Python que adota dois estático e dinâmico-digitar, alguns "scripts"conceitos semelhantes aos encontrados em Python e Ruby e bolinhos no teste de unidade recursos, entre outras coisas. Após mais investigação, vimos que tinha valor e conseguimos felizes aprendeu uma nova linguagem de programação geral.

Como interessantes e poderosas como linguagens de finalidade geral, no entanto, às vezes, o que é necessário não é um martelo, viu ou chave de fenda, mas um driver de 3/16 de polegada Porca;em outras palavras, quanto as ferramentas que fornecem capacidade amplas e recurso, como os desenvolvedores valor, às vezes, a ferramenta certa para o trabalho é muito, muito específicas. Esta parte, concentraremos não em um idioma que recria todos as construções concluir Turing nós estão acostumados a, mas um idioma que se concentra em uma determinada área, buscando fazê-lo bem, nesse caso, simultaneidade.

Os leitores de cuidado e completo desta revista serão Observe que este meme simultaneidade não é um desconhecidos para essas páginas. Últimos anos viu vários artigos sobre concorrência vêm aqui e do MSDN Magazine é mal exclusivo que aspecto--blogsfera, Twitterverse e portais da Web de desenvolvedor são todos os apresenta com discussões de simultaneidade, incluindo alguns naysayers que acha que essa coisa simultaneidade inteiro é outra tentativa de criar um problema fora do ar. Alguns especialistas do setor mesmo passaram até como dizer (conforme a boatos, em uma discussão de painel em uma conferência OOPSLA há alguns anos) que "simultaneidade é dragon nós deve slay na próxima década."

Alguns desenvolvedores serão estar se perguntando o que o problema realmente é--afinal, o .NET teve multithreading capacidade durante anos, via o idiom de invocação de delegado assíncrona (BeginInvoke/EndInvoke) e fornece mecanismos de segurança do thread via a construção de monitor (a palavra-bloqueio chave de translation from VPE for Csharp, por exemplo) e outra simultaneidade explícita construções de objeto (Mutex, eventos e assim por diante, de System.Threading). Portanto, todos os essa confusão novo parece fazer uma montanha fora de um molehill. Os desenvolvedores seria absolutamente certo, supondo que eles escrever código que é thread-safe (1) 100 por cento e (2) fazer aproveitar todas as oportunidades de paralelismo de tarefas ou dados em seu código por girando threads ou emprestando segmentos de pools de threads para maximizar o uso de todos os núcleos fornecido pelo hardware subjacente. (Se você estiver seguro em ambas as contagens, mova para o próximo artigo de revista. Melhor ainda, escreva um e diga-nos seus segredos.)

Várias propostas para resolver ou atenuar pelo menos, esse problema tenha sido flutuante por meio do universo do Microsoft, incluindo a TPL (biblioteca paralela de tarefas), extensões do Parallel FX (PFX), F # fluxos de trabalho assíncronos, simultaneidade e CCR (Runtime de controle) e assim por diante. Em cada um desses casos, a solução é para estender um idioma de host (geralmente translation from VPE for Csharp) de maneiras para fornecer paralelismo adicional ou para fornecer uma biblioteca que pode ser chamada de uma linguagem de finalidade geral do .NET. A desvantagem para a maioria dessas abordagens, no entanto, é porque ele depende a semântica da linguagem do host, fazer a coisa certa, da perspectiva de simultaneidade, uma opção eletivos, algo que os desenvolvedores precisam comprar em explicitamente e código apropriadamente. Isso infelizmente significa que a oportunidade de fazer o elemento errado, qualquer que seja, assim, cria um buraco de simultaneidade que código pode, por fim, se encaixam e criar um bug aguardando para ser descoberto, e isso é algo ruim. Ferramentas de desenvolvedor não devem, em ideal, permitem aos desenvolvedores fazer a coisa errada--é por isso que a plataforma .NET movido para uma abordagem de coleta de lixo, por exemplo. Em vez disso, ferramentas de desenvolvedor devem causar aos desenvolvedores, como Rico Mariani (arquiteto do Microsoft Visual Studio e arquiteto de desempenho do CLR anterior) coloca, "se enquadram em pit de sucesso"--o que os desenvolvedores encontrar fácil fazer deve ser a coisa certa e que os desenvolvedores localizar difícil fazer ou localizar impossível fazer deve ser o item errado.

Para esse fim, a Microsoft lançou como um projeto incubação um novo idioma squarely destinado ao domínio de simultaneidade, chamado "Axum." No espírito do permitindo que os desenvolvedores "enquadram pit de sucesso"Axum não é uma linguagem de finalidade geral como translation from VPE for Csharp ou Visual Basic, mas um destinados squarely o problema de simultaneidade, projetado desde o início para ser parte de um conjunto de idiomas que cooperam coletivamente para resolver um problema comercial. Em essência, Axum é um ótimo exemplo de um domínio específico, uma linguagem projetado especificamente para resolver apenas um aspecto vertical de um problema.

De redação deste artigo, Axum é rotulado como 0,2 versão e os detalhes de idioma e a operação do Axum são totalmente sujeitas a alterações, conforme indicado pelo aviso de isenção na frente do artigo. Mas entre "Enquanto escrevo isso"e "como ler isso"os principais conceitos devem permanecer razoavelmente estáveis. Na verdade, esses conceitos não são exclusivos para Axum e são encontrados em um número de outros idiomas e bibliotecas (incluindo vários dos projetos mencionado anteriormente, como F # e a CCR), isso mesmo Axum propriamente dito não torná-lo para uso disseminado, as idéias aqui são imprescindíveis para pensar sobre simultaneidade. Além disso, a idéia geral--um idioma específico para o domínio problemático--está crescendo rapidamente em uma "grande coisa"e vale examinando no seu próprio direito, embora em uma parte posterior.

Inícios

Os bits Axum para download no momento ao vivo no site Microsoft laboratórios no msdn.microsoft.com/devlabs/dd795202.aspx; Certifique-se de pegar pelo menos o .msi (ou para o Visual Studio 2008 ou para Beta 1 do Visual Studio 2010) e o guia do programador do. Depois de instalar esses, acionar Visual Studio e criar um novo projeto Axum Console Application, conforme mostrado no figura 1.


Figura 1 Projeto de aplicativo Console Axum

Substitua-o código mostrado na Figura 2. Este é o Axum "Hello World"que tem a mesma finalidade como todos os outros aplicativos de Hello World faz: Para verificar se a instalação funcionou e ter uma noção básica da sintaxe. Supondo que tudo o que compila e executa, estamos boas para ir.

Figura 2 Axum "Hello World"

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Hello
{
public domain Program
{
agent MainAgent : channel Microsoft.Axum.Application
{
public MainAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
// TODO: Add work of the agent here.
Console.WriteLine("Hello, Axum!");
PrimaryChannel::ExitCode <-- 0;
}
}
}
}

Como você pode ver, a sintaxe do Axum é altamente reminiscente de translation from VPE for Csharp, colocar Axum flexível para a categoria de idiomas conhecidos como a família C de idiomas: Ele usa chaves para denotar escopo, ponto-e-vírgula para encerrar instruções e assim por diante. Com que disse, no entanto, há obviamente algumas palavras-chave novo conhecer (domínio, agente, canal, receber, apenas para os iniciantes), além de alguns novos operadores. Formalmente, Axum não é realmente um derivativo do translation from VPE for Csharp, mas um subconjunto seletivo com novos recursos adicionados. Axum inclui todos os tipos de declaração 3.0 translation from VPE for Csharp (como lambdas e variáveis locais inferidas) e expressões (como cálculos algébricas e yield return/yield break), métodos, declarações de campo, delegados e tipos de enumeração, mas deixa classes, interfaces ou definições de tipo de estrutura/valor, declarações de operador, propriedades, campos const/locais e campos/métodos estáticos.

Ouvir que whooshing som? Que o vento corrente anteriores aos ouvidos, e se você achar um pequeno fluttering no seu stomach, há problemas. Saltar desativar cliffs é uma coisa boa.

Conceitos e sintaxe

A sintaxe Axum diretamente reflete os principais conceitos de Axum, exatamente como faz para translation from VPE for Csharp. No entanto, onde translation from VPE for Csharp tem classes, Axum tem agentes. Em Axum, um agente é uma entidade de código, mas diferentemente de um objeto tradicional, agentes nunca interagem diretamente--em vez disso, eles passam mensagens para um outro de forma assíncrona por meio de canais ou para ser mais precisas, entre as portas definidas no canal. Enviar uma mensagem em uma porta é uma operação assíncrona--a enviar imediatamente retorna--e receber uma mensagem em uma porta blocos até que uma mensagem está disponível. Dessa forma, segmentos nunca interagem no mesmos elementos de dados ao mesmo tempo, e uma grande fonte de deadlock e inconsistência efetivamente é levantada fora.

Para ver isso em ação, considere o código de Hello. O agente MainAgent implementa um canal especial, chamado de emprego, que é um canal que o tempo de execução Axum entende diretamente--irá processar argumentos de linha de comando de entrada e passá-las através PrimaryChannel canal do aplicativo, na porta CommandLines. Quando construída pelo tempo de execução, o MainAgent usa a operação de recepção (um primitivo Axum) para bloquear essa porta até que esses argumentos estão disponíveis, no ponto que a execução no construtor MainAgent continua. O runtime, tendo feito seu trabalho, blocos na porta Código_de_saída de PrimaryChannel, que é o sinal de que o aplicativo foi concluído. Enquanto isso, os rolos MainAgent através da matriz, imprimindo cada uma e depois concluído, envia o inteiro literal 0 para a porta Código_de_saída. Isso é recebido pelo runtime, em seguida, encerra o processo.

Observe como Axum, desde o início muito, está tomando essa idéia de execução simultânea seriamente--não só são desenvolvedores sheltered da criação e manipulação de segmentos ou objetos de simultaneidade/bloqueio, mas o idioma Axum assume uma abordagem simultânea mesmo para algo tão simples quanto Hello. Para algo tão simples quanto Hello, indiscutivelmente, este é um desperdício de tempo--no entanto, a maioria de nós não escrever aplicativos simples como Hello com muita freqüência e aqueles que fazer, poderá sempre voltar ao idioma do aplicativo tradicional orientado a objeto único-threaded-por-padrão (translation from VPE for Csharp ou Visual Basic ou qualquer outra apresenta seu trabalhar).

Figura 3 Processo aplicativo

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Process
{
public domain Program
{
agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
for (var i = 0; i < args.Length; i++)
ProcessArgument(args[i]);
PrimaryChannel::ExitCode <-- 0;
}
function void ProcessArgument(String s)
{
Console.WriteLine("Got argument {0}", s);
}
}
}
}

Hello dizer é para Wimps

Um exemplo um pouco mais interessante é o aplicativo de processo, como mostra a Figura 3. Nesta versão, vemos um novo conceito Axum, a função. Funções são diferentes dos métodos tradicionais, o compilador Axum garante (por meio de erro do compilador) que o método nunca modifica o estado interno do agente de;em outras palavras, funções podem não ter efeitos colaterais. Impedir efeitos colaterais adiciona a utilidade do Axum como um idioma de simultaneidade amigável--sem efeitos colaterais, torna extremamente difícil acidentalmente gravação código onde um thread modifica o estado compartilhado (e, portanto, apresenta resultados incorretos ou inconsistência).

Mas ainda não pronto aqui. Axum não apenas quer tornar mais fácil escrever código de thread-safe, que ele também deseja tornar mais fácil escrever código de segmento amigável. Com dois e quatro principais máquinas se tornando mais base e máquinas de 8 e 16 núcleos visíveis no horizonte, é importante procurar oportunidades para realizar operações em paralelo. Axum torna isso mais fácil, novamente levantar a conversação fora de threads e fornecendo a algumas construções de nível mais alto para trabalhar com. Figura 4 mostra uma versão modificada do ProcessAgent.

Duas novas coisas estão acontecendo aqui: um, criamos um ponto de interação, que pode servir como uma fonte ou um destinatário de mensagens (nesse caso, ele será ambos);e dois, nós tiver usado o operador de encaminhamento para criar um pipeline de mensagens do ponto de interação para a função ProcessArgument. Fazendo isso, qualquer mensagem enviada para "os argumentos"ponto de interação será encaminhado para ProcessArgument, bem, processamento. A partir daí, tudo o que resta é iterar os argumentos de linha de comando e enviar cada um deles como uma mensagem (via o operador de envio, o <--operação dentro do loop) para a interação de argumentos ponto e o trabalho começa.

(Axum formalmente a define como uma rede de fluxo de dados e há muito o que ele pode fazer além disso, para criar um simples pipeline de 1 a 1, mas que está fora do escopo desta introdução. Guia do programador do tem mais detalhes para aqueles que estejam interessados.)

Figura 4 Modificado versão do ProcessAgent

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Process
{
public domain Program
{
agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
// Second variation
//
var arguments = new OrderedInteractionPoint<String>();
arguments ==> ProcessArgument;
for (var i = 0; i < args.Length; i++)
arguments <-- args[i];
PrimaryChannel::ExitCode <-- 0;
}
function void ProcessArgument(String s)
{
Console.WriteLine(“Got argument {0}”, s);
}
}
}
}

Por agora colocar os dados pelo pipeline, Axum pode decidir como e quando a rotação segmentos para fazer o processamento. Neste exemplo simples, talvez não seja útil ou eficiente para fazer isso, mas para cenários mais sofisticados, freqüentemente poderá. Como o ponto de interação de argumentos é um ponto de interação ordenada, as mensagens enviadas para baixo o pipeline--e, mais importante, os resultados retornados--irá preservar seu lugar na ordem, mesmo se cada mensagem é processada em um segmento separado.

Além disso, esse conceito de canalização mensagens de um ponto de interação para outro é um conceito poderoso Axum emprestada de linguagens funcionais como F #. Figura 5 mostra como é fácil para adicionar algum processamento adicional no pipeline.

Observe que, novamente, a função UpperCaseIt não está modificando o estado interno do ProcessAgent mas estiver funcionando apenas com o objeto entregue. Além disso, o pipeline é modificado para incluir UpperCaseIt antes da função ProcessArgument e a intuitiva coisa acontece--os resultados de UpperCaseIt são passados para a função ProcessArgument.

Como um exercício mental, considere quantas linhas de código translation from VPE for Csharp seriam necessária para fazer isso, que tenham em mente que tudo isso também é que está sendo feita por vários threads, não apenas um único segmento de execução. Agora, imagine depurar o código para verificar se que ele é executado corretamente. (Na verdade, imaginar que ele não é necessário--use uma ferramenta como ILDasm ou Reflector para examinar a IL gerada. Definitivamente não é trivial.)

A propósito, tenho um pequeno confissão fazer--como escrito, meu exemplo não executa corretamente. Quando o código anterior é executado, o aplicativo retornará sem exibir nada. Isso não é um bug em bits Axum;Isso é como comportamento pretendido e realça como programação em um pensamento simultânea requer uma mudança mental.

Quando o ponto de interação argumentos está recebendo os argumentos de linha de comando via o operador de envio (<--), as envia é feita assincronicamente. O ProcessAgent não é bloqueio quando ele envia essas mensagens e, portanto, se o pipeline é suficientemente complexo, que os argumentos são todos enviados, o loop é encerrado e o Código_de_saída é enviado, encerrando o aplicativo, todos os antes de tudo pode acessar o console.

Para corrigir isso, ProcessAgent precisa bloquear até que o pipeline de conclusão da operação;ele precisa manter o thread ativo com algo como um Console.ReadLine(). (Isso acontece para ser complicado prática--consulte o blog da equipe Axum para os detalhes.) Ou precisamos alterar a maneira ProcessAgent funciona e é este último curso que vou tirar, principalmente para demonstrar mais alguns dos recursos do Axum.

Em vez de fazer o trabalho em si, ProcessAgent irá adiar o trabalho para um novo agente. Mas agora, apenas para tornar as coisas um pouco mais interessante, esse novo agente será também deseja saber se o argumento deve ser em maiúsculas ou minúsculas. Para fazer isso, o novo agente será necessário definir uma mensagem mais complexa, que usa não apenas a cadeia de caracteres para operar em, mas também um valor booleano (true para maiúsculas). Para fazer isso, Axum requer que definimos um esquema.

Figura 5 Pipelining mensagens

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Process
{
public domain Program
{
agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
// Third variation
//
var arguments = new OrderedInteractionPoint<String>();
arguments ==> UpperCaseIt ==> ProcessArgument;
for (var i = 0; i < args.Length; i++)
arguments <-- args[i];
PrimaryChannel::ExitCode <-- 0;
}
function String UpperCaseIt(String it)
{
return it.ToUpper();
}
function void ProcessArgument(String s)
{
Console.WriteLine("Got argument {0}", s);
}
}
}
}

Processar meu argumento

Implicitamente até este ponto, um dos objetivos de Axum foi isolar entidades diferentes execução (agentes) longe um do outro e ele tem feito isso fazer cópias das mensagens e entregando os para o agente, em vez de passar objetos diretamente. Nenhum estado compartilhado--mesmo por meio de parâmetros--significa não chances acidentais de conflito de thread.

Enquanto isso funciona bem para tipos definidos no .NET Base Class Library, pode facilmente ser derrotada se programadores .NET não farão a coisa certa em tipos definidos pelo usuário. Para combater isso, Axum requer que um novo tipo de mensagem ser definido como um tipo de esquema--essencialmente um objeto sem os métodos e campos necessários e/ou opcionais, usando a palavra-chave esquema:

schema Argument
{
required String arg;
optional bool upper;
}

Isso define um novo tipo de dados argumento, que consiste em um campo obrigatório, arg, que contém a seqüência que deve ser feito em maiúsculas ou minúsculas, e um campo opcional, superior, indicando se ele deve ser feito superior ou inferior. Agora, podemos definir um novo canal com uma porta de solicitação/resposta que recebe mensagens de argumento e fornece mensagens de String novamente:

channel Operator
{
input Argument Arg : String; // Argument in, String out
}

Ter definido nesse canal, ele se torna relativamente trivial para gravar o agente que implementa o canal, como mostrado no Figura 6. Observe que este agente tecnicamente nunca sai seu construtor--ele simplesmente gira em um loop, chamada receber porta argumentos, bloqueio até que um argumento de instância é enviada para ele, e em seguida, enviar o resultado de ToUpper ou ToLower fazer a mesma porta (dependendo o valor de superior, é claro).

Figura 6 Agente Implementando canal

agent OperatingAgent : channel Operator
{
public OperatingAgent()
{
while (true)
{
var result = receive(PrimaryChannel::Arg);
if (result.RequestValue.upper)
result <-- result.RequestValue.arg.ToUpper();
else
result <-- result.RequestValue.arg.ToLower();
}
}
}

De que os chamadoresPerspectiva (usuários), usando o OperatingAgent não é diferente ProcessAgent próprio: criamos uma instância dela usando o método interno CreateInNewDomain e começar a postar instâncias de argumento para a porta de argumentos. Quando isso, no entanto, será retornado um objeto que eventualmente produzirá a resposta volta do agente, o que é a única maneira para coletar os resultados (via operação receive() outro), como Figura 7 mostra.

Quando executado, ele faz conforme o esperado--baseia o milissegundo atual no momento do argumento enviado, a seqüência de linha de comando será maiúsculas ou minúsculas. E, ainda, tudo isso sem qualquer interação direta thread na parte do desenvolvedor.

Portanto, little, ainda assim agora

Axum claramente representa uma maneira diferente de pensar sobre programação e apesar das suas diferenças perfeitas de translation from VPE for Csharp, implementa um número significativo de recursos encontrados em linguagens de programação mais gerais. Sua finalidade principal, no entanto, centros em torno de simultaneidade e código de segmento amigável e como tal, funciona melhor com problemas que exigem (ou se beneficiar) simultaneidade.

Felizmente, a equipe de criação Axum não tenta reinventar o idioma translation from VPE for Csharp. Como Axum compila para .NET assemblies como fazer outras linguagens. NET, é relativamente trivial para criar as partes simultâneas pesado do aplicativo no Axum (como um projeto de Class Library, instead of um Console Application) e depois chamar nela partir translation from VPE for Csharp ou do Visual Basic. A distribuição Axum é fornecido com um exemplo que faz isso (o jantar filósofos exemplo, que ilustra o problema clássico de simultaneidade em um aplicativo WinForms), e o tempo gasto com Reflector sobre as bibliotecas compilado Axum irá revelar muito sobre a superfície de interoperabilidade. Usando Axum para criar biblioteca bibliotecas podem ser chamadas de outro idioma (translation from VPE for Csharp ou Visual Basic), pode ir um longo caminho em direção ao tornar pesada simultaneidade usar consideravelmente mais acessíveis para as outras tecnologias .NET, como Web ou aplicativos da área de trabalho.

Figura 7 Usando o método CreateInNewDomain

 

agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
var opAgent = OperatingAgent.CreateInNewDomain();
var correlators = new IInteractionSource<String>[args.Length];
for (int i=0; i<args.Length; i++)
correlators[i] = opAgent::Arg <-- new Argument {
arg = args[i],
upper = ((System.DateTime.Now.Millisecond % 2) == 0) };
for (int i=0; i<correlators.Length; i++)
Console.WriteLine("Got {0} back for {1}",
receive(correlators[i]), args[i]);
PrimaryChannel::ExitCode <-- 0;
}
}

Na verdade, Axum usa um experimental translation from VPE for Csharp compilador que fornece alguns recursos interessantes e diferentes, um superconjunto de translation from VPE for Csharp 3.0 (métodos 3.0 + assíncronos), nenhuma delas está implícito para translation from VPE for Csharp 4.0, by the way. No entanto, o que isso que, é a capacidade de combinação e correspondência translation from VPE for Csharp e Axum código no mesmo projeto, algo exclusivo Axum so far. Ver que guia do programador do Axum para details.Axum tem recursos adicionais não descritos aqui, abordado em alguns detalhes no guia do programador do Axum, incluindo um relacionamento estreito com o WCF para facilitar a escrever serviços de grande escala, mas esta introdução deve ser suficiente para começar a criar aplicativos, bibliotecas ou serviços em Axum. Lembre-se que esse é um projeto de pesquisa, e os usuários podem oferecer comentários via os fóruns Axum no site da DevLabs Web.

E se Axum falhar para transformar em um produto de remessa ou leitores quiser usá-lo antes? Para os iniciantes, do Axum sucesso ou fracasso depende significativamente em parte usuário reação e então irá ao vivo ou the por comentários de qualidade--enviar a equipe Axum seus pensamentos e agitate para sua versão de maneira pública! Mas mesmo se Axum falhar se formar, lembre-se, os conceitos por trás Axum não são exclusivos. Na phraseology acadêmico, Axum incorpora o modelo de atores de simultaneidade e vários outros modelos de atores estão disponíveis para o desenvolvedor .NET que quer algo para produção hoje. O projeto open-source "Retlang"incorpora várias esses conceitos, como a linguagem F # (procure no tipo de MailboxProcessor do F #) ou o Microsoft controle Runtime CCR (Concurrency and), que são status de próxima entrega, se não já existem.

No final, lembre-se, o objetivo é para não criar projetos que usam todos os idiomas baseados em CLR na existência, mas para localizar os idiomas que podem resolver problemas específicos e usá-los quando a situação.

E lembre-se, Quot linguas calles, tot homines vales.

Ted Neward é a entidade com Neward and Associates, através do qual ele fala, gravações e coaches na criação de sistemas empresariais confiável. Ele é escrito diversos livros, ensinado e falado em conferências em todo o mundo, é um palestrante da INETA e recebeu o prêmio MVP em três áreas diferentes de experiência. Entrar no de ted@tedneward.com ou pelo seu blog em blogs.tedneward.com.