Nota:
A partir do Go 1.5, GOMAXPROCS é definido como o número de núcleos do hardware: golang.org/doc/go1.5#runtime , abaixo da resposta original antes de 1.5.
Quando você executa o programa Go sem especificar a variável de ambiente GOMAXPROCS, as rotinas do Go são agendadas para execução em um único thread do sistema operacional. No entanto, para fazer o programa parecer multithread (é para isso que servem os goroutines, não são?), O agendador Go deve às vezes mudar o contexto de execução, para que cada goroutine possa fazer seu trabalho.
Como eu disse, quando a variável GOMAXPROCS não é especificada, o tempo de execução do Go só pode usar um thread, então é impossível alternar os contextos de execução enquanto o goroutine está realizando algum trabalho convencional, como cálculos ou mesmo IO (que é mapeado para funções C simples ) O contexto pode ser alternado somente quando primitivas de concorrência Go são usadas, por exemplo, quando você liga vários canais, ou (este é o seu caso) quando você diz explicitamente ao planejador para alternar os contextos - é para isso que runtime.Gosched
serve.
Portanto, em resumo, quando o contexto de execução em uma goroutine atinge a Gosched
chamada, o planejador é instruído a alternar a execução para outra goroutine. No seu caso, existem dois goroutines, principal (que representa o thread 'principal' do programa) e adicional, aquele com o qual você criou go say
. Se você remover a Gosched
chamada, o contexto de execução nunca será transferido do primeiro goroutine para o segundo, portanto, nenhum 'mundo' para você. Quando Gosched
está presente, o escalonador transfere a execução em cada iteração de loop da primeira goroutine para a segunda e vice-versa, então você tem 'hello' e 'world' intercalados.
Para sua informação, isso é chamado de 'multitarefa cooperativa': os goroutines devem ceder explicitamente o controle a outros goroutines. A abordagem usada na maioria dos sistemas operacionais contemporâneos é chamada de 'multitarefa preemptiva': threads de execução não estão preocupados com a transferência de controle; o planejador alterna os contextos de execução de forma transparente para eles. A abordagem cooperativa é freqüentemente usada para implementar 'threads verdes', ou seja, corrotinas simultâneas lógicas que não mapeiam 1: 1 para threads do sistema operacional - é assim que o tempo de execução Go e suas goroutinas são implementados.
Atualizar
Eu mencionei a variável de ambiente GOMAXPROCS, mas não expliquei o que é. É hora de consertar isso.
Quando esta variável é definida como um número positivo N
, o tempo de execução Go será capaz de criar até N
encadeamentos nativos, nos quais todos os encadeamentos verdes serão agendados. Thread nativo: um tipo de thread criado pelo sistema operacional (threads do Windows, pthreads etc). Isso significa que se N
for maior que 1, é possível que goroutines sejam programadas para executar em diferentes threads nativas e, conseqüentemente, rodar em paralelo (pelo menos, até as capacidades do seu computador: se o seu sistema for baseado em processador multicore, é provável que esses threads sejam realmente paralelos; se o seu processador tiver um único núcleo, a multitarefa preemptiva implementada nos threads do sistema operacional criará uma visibilidade de execução paralela).
É possível definir a variável GOMAXPROCS usando a runtime.GOMAXPROCS()
função em vez de pré-definir a variável de ambiente. Use algo assim em seu programa em vez do atual main
:
func main() {
runtime.GOMAXPROCS(2)
go say("world")
say("hello")
}
Neste caso, você pode observar resultados interessantes. É possível que você obtenha linhas 'hello' e 'world' impressas intercaladas de maneira desigual, por exemplo
hello
hello
world
hello
world
world
...
Isso pode acontecer se goroutines estiverem programadas para separar threads de sistema operacional. É assim que funciona a multitarefa preemptiva (ou processamento paralelo no caso de sistemas com vários núcleos): threads são paralelos e sua saída combinada é indeterminística. BTW, você pode deixar ou remover a Gosched
chamada, parece não ter efeito quando GOMAXPROCS é maior que 1.
A seguir está o que obtive em várias execuções do programa com runtime.GOMAXPROCS
chamada.
hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
Veja, às vezes a saída é bonita, às vezes não. Indeterminismo em ação :)
Outra atualização
Parece que nas versões mais recentes do compilador Go, o tempo de execução do Go força as goroutines a renderem não apenas no uso de primitivos de simultaneidade, mas também nas chamadas do sistema operacional. Isso significa que o contexto de execução pode ser alternado entre goroutines também em chamadas de funções IO. Consequentemente, em compiladores Go recentes, é possível observar um comportamento indeterminístico mesmo quando GOMAXPROCS não está definido ou está definido como 1.