Mudanças entre as edições de "Go: Processos e Concorrência"
(20 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
Linha 2: | Linha 2: | ||
= Concorrência = | = Concorrência = | ||
+ | |||
+ | {{tip|Para entendermos melhor essa parte é bom rebuscar fundamentos dobre o [[Processo]] em Sistemas Operacionais.}} | ||
A linguagem de programação Go trabalha com concorrência usando as <code>gorotines</code>. Elas exencutam funções paralelamente. É mais ou menos o conceito de ''threads'', mas são mais leves, pois o custo da criação de ''gorotines'' é menor do que se comparado a uma ''thread''. | A linguagem de programação Go trabalha com concorrência usando as <code>gorotines</code>. Elas exencutam funções paralelamente. É mais ou menos o conceito de ''threads'', mas são mais leves, pois o custo da criação de ''gorotines'' é menor do que se comparado a uma ''thread''. | ||
Linha 7: | Linha 9: | ||
; Vantagens das Gorotines | ; Vantagens das Gorotines | ||
− | * Mais leves | + | * Mais leves computacionamente, ocupando menos memória na pilha e a pilha pode alterar seu tamanho conforme a necessidade da aplicação. As ''threads'' possuem tamanho de pilha fixo; |
* As ''gorotines'' são multiplexadas para um número menor de ''threads'' do Sistema Operacional. Ou seja, pode haver uma única ''thread'' no programa com várias ''gorotines'' executando. Caso uma ''thread'' seja bloqueada por uma ''goritne'', as outras ''gorotines'' que estão nela podem ser movidas para outra ''thread''; | * As ''gorotines'' são multiplexadas para um número menor de ''threads'' do Sistema Operacional. Ou seja, pode haver uma única ''thread'' no programa com várias ''gorotines'' executando. Caso uma ''thread'' seja bloqueada por uma ''goritne'', as outras ''gorotines'' que estão nela podem ser movidas para outra ''thread''; | ||
* ''Goroutines'' se comunicam por meio de <code>canais</code>, que previnem que ''race conditions'' aconteçam ao acessar memória compartilhada. | * ''Goroutines'' se comunicam por meio de <code>canais</code>, que previnem que ''race conditions'' aconteçam ao acessar memória compartilhada. | ||
Linha 32: | Linha 34: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | = Subprocessos = | ||
+ | |||
+ | Estou falando em subprocessos, mas estamos implementando ''gorotines'' na linguagem go. | ||
+ | |||
+ | Veja que no exemplo, vamos criar uma gorotine que será filha do processo principal. Nas duas execuções, colocamos algumas linhas de execução e um pequeno delay aleatório. | ||
+ | |||
+ | O problema é que caso o programa termine sem ter terminado a gorotine chamada por ele, ela fecha também. Para contornar esse problema, vamos usar um sistema de sincronização via WAIT. | ||
+ | |||
+ | A gente cria uma variável que será um objeto do tipo <code>sync.WaitGroup</code> e para cada gorotine instanciada, a gente faz um incremento no objeto. | ||
+ | |||
+ | <syntaxhighlight lang=go> | ||
+ | var wg sync.WaitGroup | ||
+ | wg.Add(1) | ||
+ | go child(&wg) // child é nossa função para paralelizar | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | De dentro da função '''child''', que irá executar em paralelo, a gente já de início dá um '''defer''' para executar um Done no objeto WaitGroup. Cada done feito no objeto, é decrementado em um uma variável interna que controla a quantidade de gorotines executando. | ||
+ | |||
+ | <syntaxhighlight lang=go> | ||
+ | defer wg.Done() | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Não podemos esquecer que vamos ter que passar o objeto do WaitGroup como parâmetro de referência, então nossa função tem que estar preparada pra isso. | ||
+ | |||
+ | <syntaxhighlight lang=go> | ||
+ | func child(wg *sync.WaitGroup) { //... | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Ao final do programa principal, a gente usa um WAIT para agardar até que a gorotine tenha terminado. | ||
+ | |||
+ | <syntaxhighlight lang=go> | ||
+ | wg.Wait() | ||
+ | </syntaxhighlight> | ||
− | + | Para só então dar sequência e fechar o programa. | |
− | <syntaxhighlight lang=go | + | <syntaxhighlight lang=go> |
package main | package main | ||
Linha 71: | Linha 106: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | = | + | = Muitos Filhos = |
− | |||
− | |||
− | + | Quando um processo principal (pai) cria vários filhos, pode ser necessário aguardar que todos eles terminem seu processamento. Para isso, usamos uma forma de sincronização para o pai aguardar os filhos fecharem. | |
− | |||
− | |||
− | |||
− | + | O exemplo abaixo cria n filhos e aguarda eles terminarem. Ele utiliza o <code>sync.WaitGroup</code> para sincronizar os processos. | |
<syntaxhighlight lang=go> | <syntaxhighlight lang=go> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
package main | package main | ||
Linha 157: | Linha 143: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | = Referências = | ||
+ | |||
+ | * http://cocic.cm.utfpr.edu.br/progconcorrente/doku.php?id=go |
Edição atual tal como às 09h58min de 17 de abril de 2024
Afluentes: Sistemas Distribuídos e Mobile
Concorrência
|
A linguagem de programação Go trabalha com concorrência usando as gorotines
. Elas exencutam funções paralelamente. É mais ou menos o conceito de threads, mas são mais leves, pois o custo da criação de gorotines é menor do que se comparado a uma thread.
- Vantagens das Gorotines
- Mais leves computacionamente, ocupando menos memória na pilha e a pilha pode alterar seu tamanho conforme a necessidade da aplicação. As threads possuem tamanho de pilha fixo;
- As gorotines são multiplexadas para um número menor de threads do Sistema Operacional. Ou seja, pode haver uma única thread no programa com várias gorotines executando. Caso uma thread seja bloqueada por uma goritne, as outras gorotines que estão nela podem ser movidas para outra thread;
- Goroutines se comunicam por meio de
canais
, que previnem que race conditions aconteçam ao acessar memória compartilhada.
Para chamar uma função de forma concorrente no Go, basta chamá-la com a palavra chave go na frente:
package main
import (
"fmt"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
// Vamos dar uma pausa aqui senão o programa pode terminar
// antes de executar a gorotine
time.Sleep(1 * time.Second)
fmt.Println("main function")
}
Subprocessos
Estou falando em subprocessos, mas estamos implementando gorotines na linguagem go.
Veja que no exemplo, vamos criar uma gorotine que será filha do processo principal. Nas duas execuções, colocamos algumas linhas de execução e um pequeno delay aleatório.
O problema é que caso o programa termine sem ter terminado a gorotine chamada por ele, ela fecha também. Para contornar esse problema, vamos usar um sistema de sincronização via WAIT.
A gente cria uma variável que será um objeto do tipo sync.WaitGroup
e para cada gorotine instanciada, a gente faz um incremento no objeto.
var wg sync.WaitGroup
wg.Add(1)
go child(&wg) // child é nossa função para paralelizar
De dentro da função child, que irá executar em paralelo, a gente já de início dá um defer para executar um Done no objeto WaitGroup. Cada done feito no objeto, é decrementado em um uma variável interna que controla a quantidade de gorotines executando.
defer wg.Done()
Não podemos esquecer que vamos ter que passar o objeto do WaitGroup como parâmetro de referência, então nossa função tem que estar preparada pra isso.
func child(wg *sync.WaitGroup) { //...
Ao final do programa principal, a gente usa um WAIT para agardar até que a gorotine tenha terminado.
wg.Wait()
Para só então dar sequência e fechar o programa.
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func child(wg *sync.WaitGroup) {
defer wg.Done() // decrementa o contador de processo
fmt.Println("Luke: Noooooo.....") // imprime na tela
for i := 0; i < 5; i++ { // executa o laço 5 vezes
fmt.Println("Luke: No!") // imprime n a tela
d := rand.Intn(2000) // retorna um número randômico de 0 a 1999
time.Sleep(time.Duration(d) * time.Millisecond) // aguarda um tempo
}
fmt.Println("Luke Skywalker falls.") // imprime na tela
}
func main() {
var wg sync.WaitGroup // wg é tilizado para sincronizar os processos
wg.Add(1) // incrementa o contador de processos
go child(&wg) // chama a rotina child paralelamente
fmt.Println("Vader: Luke! I am your father...") // imprime na tela
for i := 0; i < 5; i++ { // executa o laço 5 vezes
fmt.Println("Vader: Yes!") // imprime na tela
d := rand.Intn(2000) // retorna um número randômico de 0 a 1999
time.Sleep(time.Duration(d) * time.Millisecond) // aguarda um tempo
}
wg.Wait() // aguarda os subprocessos terminarem
fmt.Println("Dart Vader leaves.") // imprime na tela
}
Muitos Filhos
Quando um processo principal (pai) cria vários filhos, pode ser necessário aguardar que todos eles terminem seu processamento. Para isso, usamos uma forma de sincronização para o pai aguardar os filhos fecharem.
O exemplo abaixo cria n filhos e aguarda eles terminarem. Ele utiliza o sync.WaitGroup
para sincronizar os processos.
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func child(wg *sync.WaitGroup, id int) { // função do filho
defer wg.Done() // decrementa o sincronizador
for i := 0; i < 5; i++ { // conta até 5
fmt.Println("CHILD[", id, "] ", i) // imprime i
d := rand.Intn(2000) // pega um valor randômico
time.Sleep(time.Duration(d) * time.Millisecond) // pausa um tempo aleatório
}
fmt.Println("CHILD[", id, "] done...") // imprime a finalização
}
func main() { // programa principal
fmt.Println("START PROGRAM...") // imprime na tela
var wg sync.WaitGroup // inicializa o sincronizador
for i := 0; i < 5; i++ { // cria 5 filhos
wg.Add(1) // adiciona um no sincronizador
go child(&wg, i) //cria filho
}
wg.Wait() // aguarda todos os filhos terminarem
fmt.Println("END PROGRAM...") // imprime na tela
}