【问题标题】:Difference between the main goroutine and spawned goroutines of a Go programGo 程序的主 goroutine 和衍生 goroutine 之间的区别
【发布时间】:2017-01-22 10:50:37
【问题描述】:

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

是不是因为派生的 goroutine 堆栈大小非常小(2Kbytes),而主 goroutine 大得多? main goroutine 和 spawned goroutine 有什么区别?

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

【问题讨论】:

  • 它们是一样的。你能举个例子说明你在做什么吗?
  • @JimB 感谢您的回复。我已经包含了示例链接和修改后的代码。
  • @Amd 这是go1.7 linux/amd64
  • 您在 main 中有一个繁忙的循环。不要那样做。
  • 繁忙的循环总是错误的。没有理由为了阻塞而以 100% 的速度旋转 CPU。根本不要使用 for 循环,等待阻塞的东西。在这个例子中没有理由使用 goroutine,只需内联调用该代码。如果确实需要不进行任何操作的阻塞,可以使用空的select{},但一般情况下不需要。

标签: go goroutine grpc


【解决方案1】:

为什么 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 内核,根据您可能使用的用例等待某些操作:
- sync.WaitGroup 喜欢 this
- select {} 喜欢 this
- 频道
- time.Sleep


是不是因为生成的 goroutines 堆栈大小非常小(2Kbytes), 并且主 goroutine 更大?

不,你可以试试这两个示例,看看 goroutine 的堆栈限制是一样的:
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

【讨论】:

  • 感谢您的详尽解释!
猜你喜欢
  • 1970-01-01
  • 2016-06-29
  • 2013-08-06
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-03
  • 2021-09-08
相关资源
最近更新 更多