threads golang go goroutine grpc

golang - go threads



Diferencia entre el goroutine principal y los goroutines engendrados de un programa Go (1)

¿Por qué la pila de Goroutine es infinita?

Una de las características clave de Goroutines es su costo; son baratos de crear en términos de huella de memoria inicial (a diferencia de los 1 a 8 megabytes con un hilo POSIX tradicional) y su stack crece y se contrae según sea necesario. Esto permite a un Goroutine comenzar con una sola pila de 4096 bytes que crece y se contrae según sea necesario sin el riesgo de quedarse sin nada.

Sin embargo, hay un detalle que he retenido hasta ahora, que vincula el uso accidental de una función recursiva con un caso grave de agotamiento de memoria para su sistema operativo, y es decir, cuando se necesitan nuevas páginas de pila, se asignan del montón.

A medida que su función infinita continúa llamándose a sí misma, las nuevas páginas de pila se asignan desde el montón, permitiendo que la función continúe llamándose una y otra vez. Bastante rápidamente, el tamaño del montón excederá la cantidad de memoria física libre en su máquina, momento en el cual el intercambio pronto hará que su máquina deje de funcionar.

El tamaño del montón disponible para los programas Go depende de muchas cosas, incluida la arquitectura de su CPU y su sistema operativo, pero generalmente representa una cantidad de memoria que excede la memoria física de su máquina, por lo que es probable que su máquina cambie mucho antes de que su programa agote su montón.

ref: http://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite

Bucle vacío:

for{ }

utiliza el 100% de un Núcleo de CPU, para esperar alguna operación dependiendo del caso de uso que pueda usar:
- sync.WaitGroup como este
- select {} como este
- canales
- time.Sleep

¿Es porque el tamaño de la pila de goroutines engendrados es muy pequeño (2Kbytes), y la goroutina principal es mucho más grande?

No, puedes probar estas dos muestras para ver que el límite de la pila de goroutines es el mismo:
una goroutine principal en el patio The Go ,
prueba el segundo goroutine en The Go Playground :

package main import ( "fmt" "sync" ) var wg sync.WaitGroup func main() { wg.Add(1) go run() wg.Wait() } func run() { s := &S{a: 1, b: 2} fmt.Println(s) wg.Done() } type S struct { a, b int } // String implements the fmt.Stringer interface func (s *S) String() string { return fmt.Sprintf("%s", s) // Sprintf will call s.String() }

ambas salidas son iguales en el patio de recreo Go:

runtime: goroutine stack exceeds 250_000_000-byte limit fatal error: stack overflow

salidas en una PC con 8 GB RAM:

runtime: goroutine stack exceeds 1_000_000_000-byte limit fatal error: stack overflow

Cuando creo un servidor usando gRPC , si inicio el servidor gRPC en el proceso principal, puede tratar tantas solicitudes como (miles) de clientes. Sin embargo, si inicio el servidor como una rutina, solo puede manejar algunas solicitudes (cientos) y luego quedarse atascado. He probado y confirmado esto con un ejemplo muy simple, google.golang.org/grpc/examples/helloworld.

¿Es porque el tamaño de la pila de goroutines engendrados es muy pequeño (2Kbytes), y la goroutina principal es mucho más grande? ¿Cuál es la diferencia entre el goroutine principal y los goroutinos engendrados?

Enlace de ejemplo. Partes modificadas del ejemplo de la siguiente manera.

greeter_server / main.go

func main() { go func() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) }() for { } }

greeter_client / main.go

func main() { // Set up a connection to the server. for i := 0; i < 500; i++ { conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) for i := 0; i < 500; i++ { // Contact the server and print out its response. name := defaultName if len(os.Args) > 1 { name = os.Args[1] } r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) if err != nil { log.Fatalf("could not greet: %v", err) } log.Printf("%d''s Greeting: %s", i, r.Message) } } }