Go程序的主要goroutine和衍生goroutines之间的差异

时间:2016-09-14 14:57:53

标签: go goroutine grpc

使用gRPC创建服务器时,如果我在主进程中启动gRPC服务器,它可以处理来自客户端的请求(数千)。但是,如果我将服务器作为goroutine启动,它只能处理一些请求(数百个)并在卡住之后。我用一个非常简单的例子google.golang.org/grpc/examples/helloworld测试并证实了这一点。

是因为衍生的goroutines堆栈大小非常小(2Kbytes),而主要的goroutine要大得多吗?主要的goroutine和衍生的goroutines之间有什么区别?

示例link。该示例的修改部分如下。

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 个答案:

答案 0 :(得分:3)

为什么Goroutine的筹码无限:

  

Goroutines的一个主要特点是成本;它们很便宜   根据初始内存占用创建(而不是1到8)   带有传统POSIX线程的兆字节)和它们的堆栈增长和   必要时缩小。这允许Goroutine以单一开始   4096字节堆栈,根据需要增长和缩小,没有风险   永远不见了。

     

但是到目前为止,我已经隐瞒了一个细节   意外使用递归函数来处理严重的内存   耗尽操作系统,也就是新堆栈时   需要页面,它们是从堆中分配的。

     

随着您的无限功能继续调用自身,新的堆栈页面   从堆中分配,允许函数继续   一遍又一遍地呼唤自己。堆的大小相当快   将超过您机器中的可用物理内存量   哪一点交换很快就会使你的机器无法使用。

     

Go程序可用的堆大小取决于很多   事情,包括CPU的架构和操作   系统,但它通常表示超出的内存量   您的机器的物理内存,因此您的机器可能会交换   在你的程序耗尽它之前很久。

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

空循环:

for{
}

使用100%的CPU Core,根据您使用的用例等待某些操作:
  - sync.WaitGroup,如this   - select {},如this   - 频道
  - time.Sleep

  

是因为衍生的goroutines堆栈大小非常小(2Kbytes),   而主要的goroutine更大?

不,您可以尝试这两个样本来查看goroutines的堆栈限制是否相同:
The Go Playground上的一个主要goroutine,
The Go Playground上尝试第二个goroutine:

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

Go Playground上的两个输出都是相同的:

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

的PC上输出

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