gpt4 book ai didi

go - Go 函数中具有命名返回值的数据竞争?

转载 作者:行者123 更新时间:2023-12-02 11:24:29 25 4
gpt4 key购买 nike

问题
qux() 之间的以下代码中是否存在不明显的数据竞争?写信给 err同时出现新的错误baz被退回?是否有可能 Go 内部处理 named return values将新变量的显式返回视为对命名返回值的事实上的赋值,从而允许数据竞争?

func foo(baz time.Duration) (bar *Bar, err error) {
done := make(chan struct{})

go func() {
bar, err = qux()
close(done)
}()

select {
case <-done:
return
case <-time.After(baz):
return nil, errors.New("baz")
}
}
这对我来说没有任何意义,因为我期望 errors.New() 的结果在上面的代码中,应该分配到与预初始化的 err 不同的地址.然而,我有理由怀疑这正是发生在我身上的事情。
背景
我们的一项服务每秒在集群中的多个 Pod 上执行的 HTTP 请求不到一百万次。平均每 5 亿次左右的请求,我们在检查包装错误时发生不可恢复的故障 panic here在标准库中。当非零指针指向错误的内存地址时,通常会发生这种情况,这表明并发写入。
unexpected fault address 0x0
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x80 addr=0x0 pc=0x40ebfa]

goroutine 7431988 [running]:
runtime.throw(0x1bcf57c, 0x5)
/usr/local/go/src/runtime/panic.go:1116 +0x72 fp=0xc0026e8d38 sp=0xc0026e8d08 pc=0x43bef2
runtime.sigpanic()
/usr/local/go/src/runtime/signal_unix.go:727 +0x405 fp=0xc0026e8d68 sp=0xc0026e8d38 pc=0x452805
runtime.getitab(0x195cae0, 0xe93824548b483024, 0x1, 0x7f7fc8037af8)
/usr/local/go/src/runtime/iface.go:39 +0x3a fp=0xc0026e8db8 sp=0xc0026e8d68 pc=0x40ebfa
runtime.assertI2I2(0x195cae0, 0xdcd388, 0x0, 0x7f7fc8037af8, 0xc018808a20, 0xc012bbaa01)
/usr/local/go/src/runtime/iface.go:472 +0x6a fp=0xc0026e8de8 sp=0xc0026e8db8 pc=0x41004a
errors.Is(0x1dd3f20, 0xc0120b6780, 0x1dd2a60, 0xc00003c1c0, 0xc019510c60)
/usr/local/go/src/runtime/wrap.go:49 +0xd8 fp=0xc0026e8e50 sp=0xc0026e8de8 pc=0xaacbf8
我们运行的代码看起来或多或少是这样的:
func (c *client) Get(ctx context.Context, query Query) (Results, error) {
b, err := c.fetchAndLearn(ctx, query)
if errors.Is(err, context.Canceled) { // Panic happens on this line
// ...
}

// ...
}

func (c *client) fetchAndLearn(ctx context.Context, query Query) (response *http.Response, err error) {
done := make(chan struct{})

go func() {
response, err = c.fetch(ctx, query)
close(done)
}()

select {
case <-done:
return
case <-clock.After(c.getTimeout(query)):
return nil, xerrors.New("timeout exceeded")
}
}
在毫无结果的调试之后,我想知道 Go 是否在底层做了一些不直观的事情,因为这里使用了命名返回值。有了这种微弱的直觉,我通过删除命名返回值并声明单独的错误变量来解决无法恢复的 panic 问题。
func (c *client) fetchAndLearn(ctx context.Context, query Query) (*http.Response, error) {
done := make(chan struct{})
var response *http.Response
var err error

go func() {
response, err = c.fetch(ctx, query)
close(done)
}()

select {
case <-done:
if err != nil {
return nil, xerrors.Errorf(": %w", err)
}
return response, nil
case <-clock.After(c.getTimeout(query)):
return nil, xerrors.New("timeout exceeded during request duration learning")
}
}
但这对我来说仍然没有多大意义,如果有人对 Go 内部工作原理有很好的理解,我将不胜感激,或者证实或否认我的怀疑。

最佳答案

回答
是的,根据 documentation 将命名返回函数中的显式返回写入预初始化的命名结果.
背景
如果您运行以下代码,您会注意到即使在 foo()预初始化的 val在初始赋值后从不直接操作它在 val2 之后更改内容被退回。

package main

import (
"fmt"
"time"
)

func main() {
val := foo()
fmt.Printf("Main: %+v\n", val.Bar) // explicit
time.Sleep(2 * time.Second)
}

type Foo struct {
Bar string
}

func foo() (val *Foo) {
val = &Foo{Bar: "named"}
go func() {
time.Sleep(time.Second)
fmt.Printf("Goroutine: %+v\n", val.Bar) // explicit
}()

val2 := &Foo{Bar: "explicit"}

return val2
}
发生这种情况是因为在 Go 中,每个函数启动时都会初始化一个零值返回参数。然后在返回时将任何返回值分配给预初始化的值。在我们的示例中,返回 val2分配给 val ,然后由函数返回。这开启了数据竞争的可能性,因为即使您将值分配给 val2最终将分配给 val也。
我唯一的借口是,截至撰写本文时,官方 effective Go guide有点不直观,因为它说命名结果已初始化并与未修饰的返回相关联,该返回错误地假定 显式返回不绑定(bind) .归功于 go101 背后的乐于助人的人.

关于go - Go 函数中具有命名返回值的数据竞争?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64355668/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com