Go: Compartilhe Memória Comunicando

Vamos explorar o slogan de simultaneidade do Go:

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

fonte

Antes, vamos em frente, vamos apenas levantar nossas mãos em desespero e dizer: “O que isso significa? !!”

A simultaneidade de Go é semelhante aos pipelines Unix e aos processos sequenciais de comunicação (CSP) de Hoare. Já que estamos falando sobre o compartilhamento de memória, entender os modelos de memória acima pode nos dar uma ideia do que está acontecendo.

Pipelines Unix

Ken Thompson (co-inventor do Go) adicionou pipes aos sistemas Unix em 1973. Um pipe unix é exatamente como o nome sugere: ele canaliza a saída de um programa como entrada do outro. Cada tubo tem seu próprio buffer de tubo. Um processo lê / grava no buffer do pipe para entrada / saída, respectivamente. E se houver vários processos de leitura / gravação no mesmo tubo?

Algo assim:

$ps1|ps2

Aqui ps1 grava no pipe, enquanto ps2 lê. Não precisamos de algum tipo de sincronização entre os dois processos? Então, você sabe, o ps2 deve ler o pipe apenas quando houver uma mensagem a ser lida. Antes de prosseguirmos, vamos definir nossos termos aqui: Sincronização é um acordo entre dois processos para uma certa seqüência de leitura / escrita, Mensagem são os dados sendo lidos / escritos em um tubo.

Se soubéssemos qual era o acordo e quem era o mediador, poderíamos ter uma ideia melhor. Nas páginas de manual do pipe:

 If a process attempts to read from an empty  pipe,  then read(2)  will
block
until data is available. If a process attempts to write to a
full pipe
(see below), then write(2) blocks until sufficient data has
been read
from the pipe to allow the write to complete. Non-blocking
I
/O is possible by using the fcntl(2) F_SETFL operation to enable the
O_NONBLOCK open file status flag
.

fonte

Como você pode ver, o ps1 está compartilhando memória com o ps2, comunicando-lhe uma mensagem que é sincronizada de acordo com o acordo mediado pelo kernel. Se, “Bogus! Como é compartilhar memória através da comunicação? Você poderia até dizer o contrário. Buu …” é o que você está pensando, considere o seguinte: Nem o ps1 nem o ps2 têm as informações contábeis para impor o acordo. O kernel é o guarda-livros e o mediador. Portanto, ps1 e ps2 não são aqueles que estão compartilhando memória. Também para um usuário, a visão do pássaro seria: “a própria mensagem é o sincronizador”

Comunicação de processos sequenciais

CSP é uma maneira de descrever / modelar as especificações para padrões de simultaneidade e interações. As construções do CSP nos ajudarão a entender os padrões de simultaneidade do Go ainda melhor. Vamos dar um exemplo clássico de CSP da wikipedia:

Exemplo

No exemplo acima, o comportamento dos processos VendingMachine e Person dependem dos eventos coin e card. Agora podemos sincronizar os dois eventos ou apenas o evento moeda. Em ambos os casos, a escolha será determinística. Mas para um observador externo, que não sabe sobre esses eventos, ou seja, a pessoa não toma a decisão de que inserir um cartão ou moeda levará a um chocolate delicioso, a escolha não é determinística. A ideia (no contexto de Go) é evitar o não determinismo para reduzir a complexidade.

Vemos dois estilos de simultaneidade:

1. Deterministic: sequence of actions is well defined
2. Non-deterministic: sequence of actions is not defined.

É aqui que o Go se destaca. Promove a simultaneidade determinística ao provar uma sequência de ações bem definida (também conhecida como sincronização), nomeadamente através de canais que têm um emissor e um receptor cada.

Para um melhor entendimento, vamos mergulhar no acordo de sincronização que Go tem.

Modelo de Memória Go

O modelo “Acontece antes” define claramente este acordo

To specify the requirements of reads and writes, we define happens before, a partial order on the execution of memory operations in a Go program. If event e1 happens before event e2, then we say that e2 happens after e1. Also, if e1 does not happen before e2 and does not happen after e2, then we say that e1 and e2 happen concurrently.

and

Within a single goroutine, the happens-before order is the order expressed by the program.

fonte

A comunicação por canais é a melhor maneira de seguir o acordo de sincronização acima. Embora o pacote de sincronização forneça outros primitivos de nível inferior, Once e Waitgroup são os recomendados para sincronização de nível superior.

Essa abordagem também elucida, “compartilhe memória comunicando-se”. Uma vez que o estilo de simultaneidade é determinístico, a guarda de memória e a aplicação de leitura / gravação são feitas pelo tempo de execução. Não apenas isso, os primitivos de sincronização foram inteligentemente envolvidos em uma construção de nível superior chamada “Canais”. No que diz respeito ao programador, a própria mensagem é o sincronizador que reduz a complexidade e propaga grandiosidade.

Espero que agora tenhamos um melhor entendimento do modelo de simultaneidade Go. Os seguintes recursos podem ajudar:

  1. http://golang.org/doc/codewalk/sharemem/
  2. http://blog.golang.org/2010/07/share-memory-by-communicating.html
  3. http://talks.golang.org/2012/concurrency.slide