为什么在RPC服务器中不直接重用实例的自由列表?

huangapple go评论59阅读模式
英文:

golang: Why not the free list in rpc server reuse instance directly

问题

RPC server在net/rpc包中持有两个自由列表,分别用于存储Request结构体和Response结构体。Request结构体通过其next字段来维护这个列表。

// Server表示一个RPC服务器。
type Server struct {
	// ...
	freeReq  *Request  // Request自由列表的头节点
	freeResp *Response // Response自由列表的头节点
}

type Request struct {
	ServiceMethod string   // 格式: "Service.Method"
	Seq           uint64   // 客户端选择的序列号
	next          *Request // 用于Server中的自由列表
}

RPC服务器中的自由列表似乎是一个对象池。在处理RPC请求时,服务器调用getRequest从自由列表中获取一个请求实例。处理完请求后,服务器调用freeRequest将请求实例放回自由列表中。

func (server *Server) getRequest() *Request {
	server.reqLock.Lock()
	req := server.freeReq
	if req == nil {
		req = new(Request) // 自由列表为空,创建新实例
	} else {
		server.freeReq = req.next // 自由列表不为空
		*req = Request{}          // 为什么不直接重用实例?
	}
	server.reqLock.Unlock()
	return req
}

func (server *Server) freeRequest(req *Request) {
	server.reqLock.Lock()
	req.next = server.freeReq
	server.freeReq = req
	server.reqLock.Unlock()
}

我对getRequest函数感到困惑。当自由列表为空时,它按预期创建一个新实例。当自由列表不为空时,它执行*req = Request{}。我认为Request{}也会创建一个新实例。那么持有这个自由列表的意义是什么呢?

此外,我编写了一个演示程序来展示*req = Request{}格式语句的效果。

type Student struct {
	Name string
	Age  int
}

func main() {
	s := &Student{"John", 20}
	fmt.Printf("Address: %p Content: %v\n", s, s)

	*s = Student{"Frank", 18} // 类似于 *req = Request{}
	fmt.Printf("Address: %p Content: %v\n", s, s)
}

输出结果为:

Address: 0xc42000a4c0 Content: &{John 20}
Address: 0xc42000a4c0 Content: &{Frank 18}

因此,语句*req = Request{}不会改变指针的地址,但会改变内容。

英文:

RPC server in net/rpc package holds two free lists for Request struct and Response struct. Request struct maintains this list via its next field.

// Server represents an RPC Server.
type Server struct {
	// ...
	freeReq    *Request // header node of Request free list
	freeResp   *Response // header node of Response free list
}

type Request struct {
	ServiceMethod string   // format: "Service.Method"
	Seq           uint64   // sequence number chosen by client
	next          *Request // for free list in Server
}

The free list in rpc server seems to be a object pool. When handling rpc request, server calls getRequest to get a request instance from free list. After handling request, server calls freeRequest to put request instance back to free list.

func (server *Server) getRequest() *Request {
    server.reqLock.Lock()
    req := server.freeReq
    if req == nil {
	    req = new(Request) // free list is empty
    } else {
	    server.freeReq = req.next // free list isn't empty
	    *req = Request{} // Why not reuse instance directly?
    }
	server.reqLock.Unlock()
    return req
}

func (server *Server) freeRequest(req *Request) {
	server.reqLock.Lock()
	req.next = server.freeReq
	server.freeReq = req
	server.reqLock.Unlock()
}

I'm confused about the getRequest function. When free list is empty, it creates a new instance as expected. When free list isn't empty, it executes *req = Request{}. I think Request{} also creates a new insance. So what's the point of holding this free list?


In addition, I wrote a demo to show the effect of the *req = Request{} format statement.

type Student struct {
    Name string
    Age int
}

func main() {
    s := &Student{"John", 20}
    fmt.Printf("Address: %p Content: %v\n", s, s)

    *s = Student{"Frank", 18} // similar to *req = Request{}
    fmt.Printf("Address: %p Content: %v\n", s, s)
}

The output is:

Address: 0xc42000a4c0 Content: &{John 20}
Address: 0xc42000a4c0 Content: &{Frank 18}

So statement *req = Request{} doesn't change the address of pointer, but it does change the content.

答案1

得分: 1

自由列表的概念是通过重用已经创建的对象实例来减少动态内存分配的数量。

它的工作原理如下:

当第一次请求时,自由列表为空,因此将在堆上分配新的结构。当这些结构不再需要时,它们被放入自由列表以供重用。如果没有自由列表,那么下一个请求将需要在堆上创建新的请求/响应结构,这可能会导致重复执行成本高昂。有了自由列表,可以避免这种情况,因为下一个请求可以简单地重用已经分配(并停放)的对象。

我猜你对这行代码感到困惑:

*req = Request{}

与自由列表为空且使用req = new(Request)在堆上创建新对象的情况相反,这不会在堆上分配对象。

相反,它只是将已经分配的对象(从自由列表中出队的对象)重置为其默认状态,通过复制默认值。

你可以将这行代码分解为以下几步:

r := Request{} // 在栈上创建一个具有默认内容的请求(不在堆上!)
*req = r // 将默认请求的所有字段复制到req

无论在getRequest()中采取哪种路径,它始终返回一个默认初始化的请求对象-没有上一个请求的剩余内容。

英文:

The idea of the freelist is to reduce the amount of dynamic memory allocations, by reusing object instances that have already been created.

It works like this:

When the first request is made the freelist is empty, therefore a new structures will be allocated on the heap. When these are no longer required they are put in the freelist for reuse. If you wouldn't have the freelist then the next request would need to create new Request/Response structures on the heap, which might be costly to do over-and-over again. With the freelist this is avoided, as the next request can simply reuse the already allocated (and parked) objects.

I guess you are confused about this line:

*req = Request{}

Opposed to the case where the freelist is empty and a new object is created on the heap with req = new(Request), this does not allocate an object on the heap.

It instead just resets the already allocated object (which was dequed from the freelist) to it's default state, by copying the default values.

You could decompose the line into the following:

r := Request{} // Create a request with default content on stack (not heap!)
*req = r // Copy all fields from default request to req

Whatever path is taken in getRequest(), it always returns a default initialized request object - with no leftovers from the previous request.

huangapple
  • 本文由 发表于 2017年4月11日 19:50:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/43345097.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定