Golang中每个HTTP请求都会创建新的处理器对象副本吗?

Golang中每个HTTP请求都会创建新的处理器对象副本吗? 你好,

我想知道,在给定的设置中(不使用指针接收器),每个HTTP请求是否会在内存中创建 UserServiceStorage 对象的新副本,还是它们只创建一次并用于后续的HTTP请求?

谢谢

package main

import (
	"database/sql"
	"net/http"
)

func main() {
	storage := Storage{
		Database: nil, // 实际数据库连接在此处
	}

	service := Service{
		Storage: storage,
	}

	user := User{
		Service: service,
	}

	mux := http.DefaultServeMux

	mux.HandleFunc("/create", user.Post)

	// ...
}

type User struct {
	Service Service
}

func (u User) Post(w http.ResponseWriter, r *http.Request) {
	// ...
	u.Service.create( /**/ )
	// ...
}

type Service struct {
	Storage Storage
}

func (s Service) create( /**/ ) {
	// ...
	s.Storage.insert( /**/ )
	// ...
}

type Storage struct {
	Database *sql.DB
}

func (s Storage) insert( /**/ ) {
	// ...
}

更多关于Golang中每个HTTP请求都会创建新的处理器对象副本吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

假设你是因为担心副本问题才这么问的

是的,这正是我所考虑的。感谢你的解释。非常感谢。

更多关于Golang中每个HTTP请求都会创建新的处理器对象副本吗?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


为了传递给每个使用值接收器(而非指针接收器)调用的函数,会创建副本,因此以下每次调用都会生成副本:

func (u User) Post(w http.ResponseWriter, r *http.Request)
func (s Service) create( /**/ )
func (s Storage) insert( /**/ )

(*http.ServeMux).HandleFunchandler 参数类型为 func(http.ResponseWriter, *httpRequest),因此 user.Post 实际上被“包装”进了一个闭包中。这样,每次 ServeMux 调用 handler 时,User 都会被复制到 Post 的栈帧中以便执行。接着,当 Post 调用 Service.create 时,它同样会将其内嵌的 Service 复制到 create 的栈帧中,依此类推……

话虽如此,在你的示例中,sizeof(User{}) == sizeof(Service{}) == sizeof(Storage{}) == sizeof(*sql.DB),所以你实际上只是在传递一个指针,这效率极高,因此你无需担心(假设你提问是因为担心副本问题)。

在Go的HTTP处理中,每个请求不会创建新的UserServiceStorage对象副本。这些对象在程序启动时只创建一次,然后被所有HTTP请求共享使用。

具体分析如下:

  1. 对象初始化:在main()函数中,StorageServiceUser对象各创建一次
  2. 方法绑定mux.HandleFunc("/create", user.Post)只是将user.Post方法注册为处理器,不会复制user对象
  3. 请求处理:当HTTP请求到达时,Go会调用已注册的处理器函数,但不会复制接收器对象

验证示例:

package main

import (
    "fmt"
    "net/http"
    "sync/atomic"
)

type Counter struct {
    count int64
}

func (c Counter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    current := atomic.AddInt64(&c.count, 1)
    fmt.Fprintf(w, "Request count: %d\n", current)
    fmt.Printf("Memory address: %p\n", &c)
}

func main() {
    counter := Counter{}
    fmt.Printf("Initial address: %p\n", &counter)
    
    http.Handle("/", counter)
    http.ListenAndServe(":8080", nil)
}

运行这个程序并发送多个请求,你会发现:

  • counter对象的内存地址始终相同
  • 所有请求共享同一个计数器实例

重要注意事项

  1. 由于使用值接收器,在方法调用时会发生接收器的值复制
  2. 但基础对象(UserServiceStorage)在内存中只有一份
  3. 如果需要在方法中修改对象状态,应该使用指针接收器

示例展示值复制的影响:

func (u User) Post(w http.ResponseWriter, r *http.Request) {
    // 这里的u是原始user对象的副本
    // 对u.Service的修改不会影响原始user对象
    fmt.Printf("Post method u address: %p\n", &u)
}

在你的代码中,虽然方法调用时会发生值复制,但底层的数据结构(如sql.DB连接)是通过指针共享的,所以数据库连接不会被复制。

回到顶部