Files
build-web-application-with-…/pt-br/02.7.md
Gustavo Kuklinski 7ef80e84bf Tradução ddas Concorrencias
tradução da páginas de concorrencias em Go
2019-02-16 19:31:57 -02:00

243 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Concorrência
Diz-se que Go é a linguagem C do século XXI. Eu acho que existem duas razões: primeiro, o Go é uma linguagem simples; segundo, a simultaneidade é um tema importante no mundo atual, e o Go suporta esse recurso no nível da linguagem.
## goroutine
goroutines e simultaneidade são incorporados ao design central do Go. Eles são semelhantes aos tópicos, mas funcionam de maneira diferente. Mais de uma dúzia de goroutines talvez tenham apenas 5 ou 6 threads subjacentes. Go também lhe dá suporte total para compartilhar memória em seus goroutines. Uma goroutine geralmente usa 4 ~ 5 KB de memória de pilha. Portanto, não é difícil executar milhares de goroutines em um único computador. Uma goroutine é mais leve, mais eficiente e mais conveniente que os threads do sistema.
Os goroutines são executados no gerenciador de encadeamentos em tempo de execução no Go. Usamos a palavra-chave `go` para criar uma nova goroutine, que é uma função no nível subjacente (*** main () é uma goroutine ***).
go hello(a, b, c)
Vamos ao exemplo:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world") // create a new goroutine
say("hello") // current goroutine
}
Retorno
hello
world
hello
world
hello
world
hello
world
hello
Vemos que é muito fácil usar a simultaneidade no Go usando a palavra-chave `go`. No exemplo acima, essas duas goroutines compartilham alguma memória, mas seria melhor seguir a receita de design: Não use dados compartilhados para se comunicar, use a comunicação para compartilhar dados.
runtime.Gosched () significa deixar a CPU executar outras goroutines e voltar em algum momento.
O agendador usa apenas um thread para executar todos os goroutines, o que significa que ele apenas implementa a simultaneidade. Se você deseja usar mais núcleos de CPU para aproveitar o processamento paralelo, é necessário chamar runtime.GOMAXPROCS (n) para definir o número de núcleos que deseja usar. Se `n <1`, nada muda. Esta função pode ser removida no futuro, veja mais detalhes sobre o processamento paralelo e simultaneidade neste [artigo(em inglês)](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
## Canais(Channels)
Os goroutines são executados no mesmo espaço de endereço de memória, portanto, você precisa manter a sincronização quando quiser acessar a memória compartilhada. Como você se comunica entre diferentes goroutines? Go usa um mecanismo de comunicação muito bom chamado `channel`. `channel` é como um pipeline bidirecional em shells Unix: use` channel` para enviar ou receber dados. O único tipo de dado que pode ser usado em canais é o tipo `channel` e a palavra-chave` chan`. Esteja ciente de que você tem que usar `make` para criar um novo` channel`.
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
canais usa, o operador `<-` para enviar ou receber dados.
ch <- v // envia v para o canal ch.
v := <-ch // recebe dados de ch, e os assina em v
Exemplos:
package main
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total // envia o total para c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // recebe de c
fmt.Println(x, y, x + y)
}
Enviando e recebendo dados em blocos de canais por padrão, é muito mais fácil usar goroutines síncronas. O que quero dizer com block é que uma goroutine não continuará ao receber dados de um canal vazio, ou seja, (`value: = <-ch`), até que outras goroutines enviem dados para este canal. Por outro lado, a goroutine não continuará até que os dados enviados a um canal, ou seja (`ch <-5`), sejam recebidos.
## Canais em Buffer
Eu introduzi canais não-bufferizados acima. Go também tem canais em buffer que podem armazenar mais de um único elemento. Por exemplo, `ch: = make (chan bool, 4)`, aqui criamos um canal que pode armazenar 4 elementos booleanos. Assim, neste canal, podemos enviar 4 elementos sem bloqueio, mas a goroutine será bloqueada quando você tentar enviar um quinto elemento e nenhuma goroutine o receber.
ch := make(chan type, n)
n == 0 ! non-bufferblock
n > 0 ! buffernon-block until n elements in the channel
Você pode tentar o seguinte código no seu computador e alterar alguns valores.
package main
import "fmt"
func main() {
c := make(chan int, 2) // altera de 2 para 1 e retornará um erro, mas 3 funciona
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
## Alcance e fechamento(Range and Close)
Podemos usar o range para operar em canais buffer como em slice e map.
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
`for i := range c` não parará de ler dados do canal até que o canal seja fechado. Usamos a palavra-chave `close` para fechar o canal no exemplo acima. É impossível enviar ou receber dados em um canal fechado; você pode usar `v, ok: = <-ch` para testar se um canal está fechado. Se `ok` retornar falso, significa que não há dados nesse canal e foi fechado.
Lembre-se sempre de fechar os canais nos produtores e não nos consumidores, ou é muito fácil entrar em status de pânico.
Outra coisa que você precisa lembrar é que os canais não são como arquivos. Você não precisa fechá-los com frequência, a menos que tenha certeza de que o canal é completamente inútil ou deseja sair de loops de intervalo.
## Select
Nos exemplos acima, usamos apenas um canal, mas como podemos lidar com mais de um canal? Go tem uma palavra-chave chamada `select` para ouvir muitos canais.
`select` está bloqueando por padrão e continua a executar somente quando um dos canais tem dados para enviar ou receber. Se vários canais estiverem prontos para usar ao mesmo tempo, selecione a opção para executar aleatoriamente.
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x + y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
`select` tem um caso` default`, assim como `switch`. Quando todos os canais não estão prontos para uso, ele executa o caso padrão (ele não aguarda mais o canal).
select {
case i := <-c:
// use i
default:
// Executa aqui quando C estiver bloqueado
}
## Timeout
Às vezes uma goroutine fica bloqueada. Como podemos evitar isso para evitar que todo o programa bloqueie? É simples, podemos definir um tempo limite no select.
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("timeout")
o <- true
break
}
}
}()
<- o
}
## Runtime goroutine
O pacote `runtime` tem algumas funções para lidar com goroutines.
- `runtime.Goexit ()`
Sai da gorout atual, mas as funções adiadas serão executadas como de costume.
- `runtime.Gosched ()`
Permite que o planejador execute outras goroutines e volte em algum momento.
- `runtime.NumCPU () int`
Retorna o número de núcleos da CPU
- `runtime.NumGoroutine () int`
Retorna o número de goroutines
- `runtime.GOMAXPROCS (n int) int`
Define quantos núcleos de CPU você deseja usar
## Links
- [Prefácio](preface.md)
- Seção anterior: [interfaces](02.6.md)
- Próxima seção: [Summary](02.8.md)