Golang Go语言中标准库http.Request的WithContext方法为何浅拷贝而非直接赋值?

发布于 1周前 作者 sinazl 来自 Go语言

如题。

// WithContext returns a shallow copy of r with its context changed
// to ctx. The provided ctx must be non-nil.
//
// For outgoing client request, the context controls the entire
// lifetime of a request and its response: obtaining a connection,
// sending the request, and reading the response headers and body.
//
// To create a new request with a context, use NewRequestWithContext.
// To make a deep copy of a request with a new context, use Request.Clone.
func (r *Request) WithContext(ctx context.Context) *Request {
	if ctx == nil {
		panic("nil context")
	}
	r2 := new(Request)
	*r2 = *r
	r2.ctx = ctx
	return r2
}

Golang Go语言中标准库http.Request的WithContext方法为何浅拷贝而非直接赋值?

更多关于Golang Go语言中标准库http.Request的WithContext方法为何浅拷贝而非直接赋值?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

10 回复

我也有这个问题诶

更多关于Golang Go语言中标准库http.Request的WithContext方法为何浅拷贝而非直接赋值?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


不改变之前的引用的行为。

在 net/http 标准库中,http.Request 中的 WithContext 方法会返回一个新的 http.Request 对象,它会将原始的请求对象复制一份,然后将新的上下文信息赋值给新的请求对象。这样做的原因是为了确保新的请求对象与原始请求对象是完全独立的,以避免在并发处理请求时可能出现的竞争条件。

具体来说,WithContext 方法会浅拷贝一份原始请求对象的所有字段,包括头部、URL 、请求体等等。然后它会将新的上下文信息赋值给新的请求对象的上下文字段,这个操作是通过直接赋值来完成的。因为上下文信息通常只是一个指针类型的数据结构,因此它的复制是非常快速和低成本的,因此可以直接进行赋值操作。

通过浅拷贝一份请求对象,可以确保新的请求对象与原始请求对象是独立的。这意味着对新的请求对象的任何修改都不会影响原始请求对象,反之亦然。这在处理并发请求时非常重要,因为多个并发请求可能会同时修改同一个请求对象,这可能会导致不可预料的结果。通过确保每个请求对象都是独立的,可以避免这种情况的发生,从而提高程序的稳定性和可靠性。

FROM ChatGPT

> Request is explicitly immutable. That’s why we have Request.WithContext.

我来啦,感谢 at !

当初提这个 pr 是因为我的 nbio 实现的 http 兼容标准库,这涉及到 Request 的 context 字段,每个请求的 context 应该是共用一个全局的,这样当全局的 context 退出(比如进程退出、cancel 时),这个 request 的 handler 中使用这个 context 就可以一块退出(比如使用 Mysql 时传入这个 context )。但这个字段不是导出的,所以没有办法直接设置。

由于标准库规则严格、不肯开放这个字段导出,至少对于官方而言,他们不考虑 nbio 要解决的海量并发连接数写成数量、内存开销、GC 等问题,所以官方也确实不需要开放导出。

无奈只能使用魔法了,预先用 WithContext 生成了一个带有公共 context 的 Request ,后面创建新 Request 时 *newReq = *reqWithCtx 整体赋值,就避过没导出不能设置的问题了。不过虽然能用,毕竟 Request 结构体有点大,这么做能用但不划算,凑合用吧,毕竟我也支持官方不随意放权。。。 :joy:

那 cancel 单个不是会导致影响其他?


单个请求,为什么会想到去 cancel 全局的呢?
单个请求自己 return 就完事了,除非这是运维发出的停止服务之类的请求。是不是这个理?嘿嘿

在Golang中,http.RequestWithContext方法设计为浅拷贝而非直接赋值,主要是出于以下几个方面的考虑:

  1. 避免数据竞争:HTTP请求对象可能会在多个goroutine之间共享。直接修改请求对象的上下文(Context)可能会导致数据竞争,因为上下文通常用于控制goroutine的生命周期和取消信号。浅拷贝确保了每个goroutine操作的是独立的上下文副本,从而避免了并发问题。

  2. 不可变性:在函数式编程和并发编程中,不可变数据通常更容易管理。浅拷贝使得原始的http.Request对象在传递过程中保持不变,这有助于减少错误并简化调试。

  3. 灵活性:通过浅拷贝,可以在不改变原始请求对象的情况下,为请求添加或修改特定的上下文信息,如超时、取消信号等。这提供了更灵活的控制方式,允许在请求的不同处理阶段使用不同的上下文。

  4. 保持一致性:标准库中的许多方法都遵循了浅拷贝的原则,以保持数据的一致性和安全性。WithContext方法遵循这一惯例,使得开发者在使用时可以更加直观和一致。

综上所述,http.RequestWithContext方法选择浅拷贝而非直接赋值,是为了在并发环境中保证数据的安全性、一致性和灵活性。这种设计符合Go语言的并发编程哲学,有助于开发者编写健壮、高效的代码。

回到顶部