243 lines
8.1 KiB
Markdown
243 lines
8.1 KiB
Markdown
# 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-buffer(block)
|
||
n > 0 ! buffer(non-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)
|