使用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)
}
}
}
答案 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