Difference between the main goroutine and spawned

2019-02-10 20:23发布

When creating a server using gRPC, if I start the gRPC server in the main process, it can deal with as many as requests (thousands) from clients. However, if I start the server as a goroutine, it can only handle some requests (hundreds) and after get stuck. I have tested and confirmed this with a very simple example, google.golang.org/grpc/examples/helloworld.

Is it because spawned goroutines stack size is very small (2Kbytes), and the main goroutine's much larger? What's the difference between the main goroutine and spawned goroutines?

Example link. Modified parts of the example as follows.

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

1条回答
霸刀☆藐视天下
2楼-- · 2019-02-10 21:05

Why is a Goroutine’s stack infinite:

One of the key features of Goroutines is their cost; they are cheap to create in terms of initial memory footprint (as opposed to the 1 to 8 megabytes with a traditional POSIX thread) and their stack grows and shrinks as necessary. This allows a Goroutine to start with a single 4096 byte stack which grows and shrinks as needed without the risk of ever running out.

There is however one detail I have withheld until now, which links the accidental use of a recursive function to a serious case of memory exhaustion for your operating system, and that is, when new stack pages are needed, they are allocated from the heap.

As your infinite function continues to call itself, new stack pages are allocated from the heap, permitting the function to continue to call itself over and over again. Fairly quickly the size of the heap will exceed the amount of free physical memory in your machine, at which point swapping will soon make your machine unusable.

The size of the heap available to Go programs depends on a lot of things, including the architecture of your CPU and your operating system, but it generally represents an amount of memory that exceeds the physical memory of your machine, so your machine is likely to swap heavily before your program ever exhausts its heap.

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


Empty loop:

for{
}

uses 100% of a CPU Core, to wait for some operation depending to the use case you may use:
- sync.WaitGroup like this
- select {} like this
- channels
- time.Sleep


Is it because spawned goroutines stack size is very small (2Kbytes), and the main goroutine's much larger?

No, you may try these two samples to see the stack limit of goroutines are the same:
one main goroutine on The Go Playground,
try second goroutine on 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()
}

both outputs are the same on the Go Playground:

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

outputs on a PC with 8 GB RAM:

runtime: goroutine stack exceeds 1_000_000_000-byte limit
fatal error: stack overflow
查看更多
登录 后发表回答