2016-09-14 1 views
3

Bei der Erstellung eines Servers mit gRPC, wenn ich den gRPC Server im Hauptprozess starten, kann es mit so vielen Anfragen (Tausende) von Clients umgehen. Wenn ich den Server jedoch als Goroutine starte, kann er nur einige Anfragen bearbeiten (Hunderte) und danach hängen bleiben. Ich habe das mit einem sehr einfachen Beispiel, google.golang.org/grpc/examples/helloworld, getestet und bestätigt.Unterschied zwischen der Haupt Goroutine und laiched goroutines eines Go-Programms

Liegt es daran, dass die Größe des Stacks der erzeugten Gauner sehr klein ist (2Kbytes) und die Haupt-Goroutine viel größer ist? Was ist der Unterschied zwischen der Haupt-Goroutine und den Laich-Gaunern?

Beispiel link. Modifizierte Teile des Beispiels wie folgt.

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) 
     } 
    } 
} 
+0

Sie sind die gleichen. Können Sie ein Beispiel zeigen, was genau Sie tun? – JimB

+0

@ JimB Danke für die Antwort. Ich habe den Beispiellink und den modifizierten Code eingefügt. – v1ct0r

+0

@Amd Es ist go1.7 linux/amd64 – v1ct0r

Antwort

2

Warum ist ein Stapel von Goroutine unendlich:

Eines der wichtigsten Features von Goroutines sind ihre Kosten; sie sind billig zu schaffen in Bezug auf die ursprüngliche Speicherbedarf (im Gegensatz zu den 1 bis 8 Megabyte mit einem traditionellen POSIX-Thread) und ihre Stapel wächst und schrumpft wie nötig. Dies ermöglicht es einer Goroutine mit einem einzelnen 4096 Byte-Stack zu beginnen, der bei Bedarf wächst und schrumpft, ohne dass das Risiko besteht, dass jemals verbraucht wird.

Es jedoch ein Detail, das ich bis jetzt zurückgehalten habe, die die versehentliche Verwendung einer rekursiven Funktion zu einem schweren Fall von Speicher Erschöpfung für Ihr Betriebssystem verbindet, und das ist, wenn neue Stapel Seiten benötigt werden, Sie werden vom Heap zugewiesen.

Da sich Ihre unendliche Funktion weiterhin selbst aufruft, werden neue Stapelseiten vom Heap zugewiesen, wodurch die Funktion fortfahren kann, sich immer wieder an anzurufen. Ziemlich schnell wird die Größe des Heap die Menge an freiem physischem Speicher in Ihrem Computer überschreiten, bei , die Punkt-Swapping wird bald Ihre Maschine unbrauchbar machen.

Die Größe des Haufens verfügbaren Programme zu gehen, hängt von vielen Dinge, einschließlich der Architektur Ihrer CPU und Betriebs System, aber es stellt in der Regel eine Menge an Speicher, der den physischen Speicher des Geräts überschreitet , also wird Ihre Maschine wahrscheinlich stark austauschen, bevor Ihr Programm jemals seinen Heap erschöpft.

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


Leere Schleife:

for{ 
} 

verwendet 100% der CPU-Kern, für einige Operation wartet, je nach Anwendungsfall können Sie verwenden:
- sync.WaitGroup wie this
- select {} wie this
- Kanäle
- time.Sleep


Ist es, weil gelaicht goroutines Stack-Größe ist sehr klein (2 KBytes), und die Haupt goroutine viel größer?

Nein, können Sie diese beiden Proben versuchen die Stapelgrenze von goroutines zu sehen, sind die gleichen:
ein Haupt goroutine auf The Go Playground,
versuchen zweiten goroutine auf 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() 
} 

beide Ausgänge sind die gleichen auf dem Go Playground:

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

Ausgänge auf einem PC mit 8 GB RAM:

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

Danke für Ihre gründliche Erklärung! – v1ct0r

Verwandte Themen