No Go não consigo descobrir como obter a seguinte configuração.
- As rotinas de go de 1º nível devem interromper suas goroutines de 2º nível antes que elas terminem.
- Se a função
runGoroutine
não estiver em um loop (não quero que ela entre em loop), então como a goroutine sairia quando uma mensagem fosse enviada ao canal? Se não houvesse loop, elacase <-stopChan:
seria chamada apenas uma vez no início. - E se a função
runGoroutine
chamar outras funções assíncronas? Elas também seriam encerradas quando o return fosse atingido?
Talvez eu esteja abordando isso errado. No meu caso, tenho x
grupos de tarefas que o usuário pode iniciar, quando o usuário inicia um grupo de tarefas, uma goroutine é feita (1º nível) para retornar o acesso do usuário CL. Este grupo de tarefas (1º nível) é uma coleção de y
solicitações de API (y é 200-300 na prática). Para cada solicitação de API, faço uma nova go-routine (2º nível). Estou mirando na velocidade; se eu estiver realizando centenas de solicitações de API por segundo, é mais rápido/eficiente colocar cada solicitação em sua própria goroutine? Ou é o mesmo fazer isso em 1 thread?
Parent/Main process
| |
Child goroutine Child goroutine
| |
X bottom goroutines X bottom goroutines (API level)
package main
import (
"fmt"
"time"
)
func runGoroutine(stopChan <-chan struct{}) {
for {
select {
case <-stopChan:
return // Exit the goroutine
default:
fmt.Println("Goroutine is working...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
stopChan := make(chan struct{})
// Start the goroutine
go runGoroutine(stopChan)
// User should be able to close the routines they want
close(stopChan)
}
Novo Código
package main
import (
"context"
"log"
"net/http"
"sync"
"time"
)
func main() {
// Initialize context with cancellation
ctx, cancel := context.WithCancel(context.Background())
// Call the worker pool in a separate goroutine to allow main to return immediately
// We can start any task group like this
go startWorkerPool(ctx, 20, 100)
// User has CL access on main thread
// Main function returns, but `cancel` is accessible to allow later cancellation if needed
log.Println("Worker pool started; you can cancel it by calling cancel()")
waitForNine()
}
func waitForNine() {
var input int
for input != 9 {
fmt.Print("Enter a number: ")
fmt.Scan(&input)
}
}
// startWorkerPool starts a fixed-size worker pool to handle `numRequests` requests.
func startWorkerPool(ctx context.Context, numWorkers, numRequests int) {
var wg sync.WaitGroup
work := make(chan string)
// Launch the specified number of workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func(ctx context.Context) {
defer wg.Done()
client := http.Client{}
for url := range work {
if req, err := http.NewRequestWithContext(ctx, "GET", url, nil); err != nil {
log.Println("failed to create new request:", err)
} else if resp, err := client.Do(req); err != nil {
log.Println("failed to make request:", err)
} else {
resp.Body.Close()
}
}
}(ctx)
}
// Enqueue URLs for workers and close the channel when done
go func() {
for i := 0; i < numRequests; i++ {
work <- "https://httpbin.org/get"
}
close(work)
}()
// Wait for all workers to finish
wg.Wait()
log.Println("All requests processed")
}
Um shared
context
é uma maneira fácil de cancelar várias goroutines de uma vez. Considere este exemplo simples:Este código inicia 100 goroutines concorrentes para solicitar
https://httpbin.org/get
, uma URL de teste conveniente. Um segundo após o programa ser iniciado, seu contexto compartilhado é cancelado, fazendo com que as solicitações http sejam interrompidas. Você pode usar um sinal ou um evento diferente para cancelar este contexto compartilhado.Iniciar goroutines tem mais overhead do que passar valores por um canal. Faz sentido iniciar um worker pool de goroutines http e distribuir o trabalho para eles.
Este próximo exemplo cria 20 goroutines, e essas 20 goroutines manipulam todas as 100 requisições. O padrão de pool de trabalhadores é mais prático quando as cargas de trabalho são muito altas.
Atualizar
Agora que temos nosso worker pool, vamos fazer com que ele puxe nossa lista de urls do stdin. O usuário pode fornecer urls digitando-as ou redirecionando o conteúdo do arquivo ou outras fontes para o stdin.
Tudo o que precisamos fazer é começar a ler nossas URLs do stdin e passá-las para nossa fila de trabalho para recuperação em segundo plano.
Só podemos receber uma certa quantidade de trabalho de uma vez. Depois desse ponto, podemos escolher entre sobrecarregar nosso computador host ou pausar a entrada de novo trabalho; geralmente, a última opção é preferível. Nessa implementação, limitamos a quantidade de trabalho que enfileiramos fornecendo um tamanho de buffer limitado para
work
.Você pode executar o programa interativamente ou fornecer as entradas de um arquivo ou outra fonte. Aqui está um exemplo de fornecimento de 3 urls na linha de comando e cancelamento imediato.
If you omit the
cancel
, the program still ends when the list of URLs is processed, and the program then completes. that's becausescanner
reaches end-of-file. You can signal end-of-file at the command line with Ctrl-D.