golang:为什么不直接在rpc server reuse instance中的free list
golang: Why not the free list in rpc server reuse instance directly
net/rpc 包中的 RPC server 包含 Request
结构和 Response
结构的两个免费列表。 Request
结构通过其 next
字段维护此列表。
// 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
}
rpc server 中的空闲列表似乎是一个对象池。处理rpc请求时,服务器调用getRequest
从空闲列表中获取一个请求实例。处理请求后,服务器调用 freeRequest
将请求实例放回空闲列表。
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()
}
我对 getRequest
函数感到困惑。当空闲列表为空时,它会按预期创建一个新实例。当空闲列表不为空时,它执行*req = Request{}
。我认为 Request{}
也创建了一个新实例。那么持有这个空闲列表有什么意义呢?
另外,我写了一个demo来展示*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} // similar to *req = Request{}
fmt.Printf("Address: %p Content: %v\n", s, s)
}
输出为:
Address: 0xc42000a4c0 Content: &{John 20}
Address: 0xc42000a4c0 Content: &{Frank 18}
所以语句*req = Request{}
不会改变指针的地址,但会改变内容。
freelist 的想法是通过重用已经创建的对象实例来减少动态内存分配量。
它是这样工作的:
当第一个请求发出时,空闲列表是空的,因此将在堆上分配一个新的结构。当不再需要这些时,它们将被放入空闲列表以供重用。如果您没有空闲列表,那么下一个请求将需要在堆上创建新的 Request/Response 结构,一遍又一遍地执行此操作可能代价高昂。使用空闲列表可以避免这种情况,因为下一个请求可以简单地重用已经分配(和停放)的对象。
我猜你对这一行感到困惑:
*req = Request{}
反对freelist为空,在堆上创建新对象的情况req = new(Request)
,这不会在堆上分配对象。
它只是通过复制默认值将已经分配的对象(从空闲列表中取出)重置为默认状态。
您可以将此行分解为以下内容:
r := Request{} // Create a request with default content on stack (not heap!)
*req = r // Copy all fields from default request to req
无论在 getRequest()
中采用什么路径,它总是 returns 默认的初始化请求对象 - 没有上一个请求的遗留物。
RPC server 包含 Request
结构和 Response
结构的两个免费列表。 Request
结构通过其 next
字段维护此列表。
// 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
}
rpc server 中的空闲列表似乎是一个对象池。处理rpc请求时,服务器调用getRequest
从空闲列表中获取一个请求实例。处理请求后,服务器调用 freeRequest
将请求实例放回空闲列表。
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()
}
我对 getRequest
函数感到困惑。当空闲列表为空时,它会按预期创建一个新实例。当空闲列表不为空时,它执行*req = Request{}
。我认为 Request{}
也创建了一个新实例。那么持有这个空闲列表有什么意义呢?
另外,我写了一个demo来展示*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} // similar to *req = Request{}
fmt.Printf("Address: %p Content: %v\n", s, s)
}
输出为:
Address: 0xc42000a4c0 Content: &{John 20}
Address: 0xc42000a4c0 Content: &{Frank 18}
所以语句*req = Request{}
不会改变指针的地址,但会改变内容。
freelist 的想法是通过重用已经创建的对象实例来减少动态内存分配量。
它是这样工作的:
当第一个请求发出时,空闲列表是空的,因此将在堆上分配一个新的结构。当不再需要这些时,它们将被放入空闲列表以供重用。如果您没有空闲列表,那么下一个请求将需要在堆上创建新的 Request/Response 结构,一遍又一遍地执行此操作可能代价高昂。使用空闲列表可以避免这种情况,因为下一个请求可以简单地重用已经分配(和停放)的对象。
我猜你对这一行感到困惑:
*req = Request{}
反对freelist为空,在堆上创建新对象的情况req = new(Request)
,这不会在堆上分配对象。
它只是通过复制默认值将已经分配的对象(从空闲列表中取出)重置为默认状态。
您可以将此行分解为以下内容:
r := Request{} // Create a request with default content on stack (not heap!)
*req = r // Copy all fields from default request to req
无论在 getRequest()
中采用什么路径,它总是 returns 默认的初始化请求对象 - 没有上一个请求的遗留物。