Skip to content

Latest commit

 

History

History
242 lines (171 loc) · 8.14 KB

File metadata and controls

242 lines (171 loc) · 8.14 KB

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).

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