为什么 Go 堆栈中的切片会被垃圾回收?

Igu*_*han 1 garbage-collection go

    \n
  1. 我编写了一个函数来复制string[]byte\xef\xbc\x9a
  2. \n
\n
func unsafeStringToBytes(xxxxxs string) []byte {\n    p := &xxxxxs\n    sh := (*reflect.StringHeader)(unsafe.Pointer(p))\n    sliceHeader := &reflect.SliceHeader{\n        Data: sh.Data,\n        Len:  sh.Len,\n        Cap:  sh.Len,\n    }\n\n    runtime.GC()\n    time.Sleep(10 * time.Nanosecond)\n\n    b := *(*[]byte)(unsafe.Pointer(sliceHeader))\n    return b\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 然后,我写一个测试:
  2. \n
\n
package gc\n\nimport (\n    "bufio"\n    "log"\n    "os"\n    "reflect"\n    "runtime"\n    "testing"\n    "time"\n    "unsafe"\n)\n\nfunc TestWriteLog(t *testing.T) {\n    f1, err := os.Create("./log")\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer f1.Close()\n\n    for i := 0; i <= 1000000; i++ {\n        _, err := f1.WriteString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\\n")\n        if err != nil {\n            t.Fatal(err)\n        }\n    }\n}\n\nfunc heapHeapHeap() {\n    var a *[]byte\n    for {\n        tmp := make([]byte, 100000000, 100000000)\n        a = &tmp\n        _ = a\n    }\n}\n\nfunc TestStringToBytes(t *testing.T) {\n    go heapHeapHeap()\n\n    f1, err := os.Open("./log")\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer f1.Close()\n\n    reader := bufio.NewReader(f1)\n    count := 1\n    var firstChar byte\n\n    for {\n        s, _ := reader.ReadString(\'\\n\')\n        if len(s) == 0 {\n            continue\n        }\n        firstChar = s[0]\n\n        // HERE BE DRAGONS\n        bytes2 := unsafeStringToBytes(s)\n\n        _, _ = reader.ReadString(\'\\n\')\n\n        if len(bytes2) > 0 && firstChar != bytes2[0] {\n            t.Fatalf("win! after %d iterations\\n", count)\n            os.Exit(0)\n        }\n\n        count++\n        //t.Log(count)\n    }\n}\n\nfunc unsafeStringToBytes(xxxxxs string) []byte {\n    p := &xxxxxs\n    sh := (*reflect.StringHeader)(unsafe.Pointer(p))\n    sliceHeader := &reflect.SliceHeader{\n        Data: sh.Data,\n        Len:  sh.Len,\n        Cap:  sh.Len,\n    }\n\n    runtime.GC()\n    time.Sleep(10 * time.Nanosecond)\n\n    b := *(*[]byte)(unsafe.Pointer(sliceHeader))\n    //runtime.KeepAlive(xxxxxs)\n    return b\n}\n
Run Code Online (Sandbox Code Playgroud)\n

首先,运行TestWriteLog生成log文件。然后我们运行TestStringToBytes

\n
$ go test -gcflags=\'-m\' -v -run TestStringToBytes                                                                                                                                                               1 \xe2\x86\xb5\n# go_test/gc [go_test/gc.test]\n./string2bytes_test.go:15:22: inlining call to os.Create\n./string2bytes_test.go:29:6: can inline heapHeapHeap\n./string2bytes_test.go:41:20: inlining call to os.Open\n./string2bytes_test.go:47:27: inlining call to bufio.NewReader\n./string2bytes_test.go:47:27: inlining call to bufio.NewReaderSize\n./string2bytes_test.go:47:27: inlining call to bufio.(*Reader).reset\n./string2bytes_test.go:14:19: leaking param: t\n./string2bytes_test.go:17:12: ... argument does not escape\n./string2bytes_test.go:24:11: ... argument does not escape\n./string2bytes_test.go:32:3: moved to heap: tmp\n./string2bytes_test.go:32:14: make([]byte, 100000000, 100000000) escapes to heap\n./string2bytes_test.go:73:26: xxxxxs does not escape\n./string2bytes_test.go:76:17: &reflect.SliceHeader{...} does not escape\n./string2bytes_test.go:38:24: leaking param: t\n./string2bytes_test.go:43:12: ... argument does not escape\n./string2bytes_test.go:47:27: new(bufio.Reader) does not escape\n./string2bytes_test.go:47:27: make([]byte, bufio.size) escapes to heap\n./string2bytes_test.go:64:12: ... argument does not escape\n./string2bytes_test.go:64:13: count escapes to heap\n./string2bytes_test.go:90:24: leaking param: s\n# go_test/gc.test\n/var/folders/6w/7dl1f7mx2pv1_v48_mg37br80000gn/T/go-build1924552004/b001/_testmain.go:39:6: can inline init.0\n/var/folders/6w/7dl1f7mx2pv1_v48_mg37br80000gn/T/go-build1924552004/b001/_testmain.go:47:24: inlining call to testing.MainStart\n/var/folders/6w/7dl1f7mx2pv1_v48_mg37br80000gn/T/go-build1924552004/b001/_testmain.go:47:42: testdeps.TestDeps{} escapes to heap\n/var/folders/6w/7dl1f7mx2pv1_v48_mg37br80000gn/T/go-build1924552004/b001/_testmain.go:47:24: &testing.M{...} escapes to heap\n=== RUN   TestStringToBytes\n    string2bytes_test.go:64: win! after 164307 iterations\n--- FAIL: TestStringToBytes (57.46s)\nFAIL\nexit status 1\nFAIL    go_test/gc  57.938s\n
Run Code Online (Sandbox Code Playgroud)\n

结果显示,它失败了。

\n
    \n
  1. 我分析测试失败的原因是,已经xxxxxs被GC了,因为当我把它放在runtime.KeepAlive(xxxxxs)函数中时,失败就再也没有出现过。
  2. \n
\n
func unsafeStringToBytes(xxxxxs string) []byte {\n    p := &xxxxxs\n    sh := (*reflect.StringHeader)(unsafe.Pointer(p))\n    sliceHeader := &reflect.SliceHeader{\n        Data: sh.Data,\n        Len:  sh.Len,\n        Cap:  sh.Len,\n    }\n\n    runtime.GC()\n    time.Sleep(10 * time.Nanosecond)\n\n    b := *(*[]byte)(unsafe.Pointer(sliceHeader))\n    runtime.KeepAlive(xxxxxs)\n    return b\n}\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 但!!!我有一个很大的问题是:
  2. \n
\n
./string2bytes_test.go:73:26: xxxxxs does not escape\n
Run Code Online (Sandbox Code Playgroud)\n

没有xxxxxs逃逸,它位于go栈中,怎么可能被GC!

\n

谁能告诉我为什么?解决方案“ xxxxxsbe GC”是错误的吗?还是有其他原因?

\n

Axe*_*ner 7

您的代码违反了unsafe.Pointer 规则。特别是,规则 6 指定不应声明或分配 的变量reflect.SliceHeader

因此,当 GC 增大/缩小/移动堆栈时,GC 可能会失去对字符串的跟踪。

请注意,在现代 Go 版本中,有一种更简单、更安全的方法来进行您想要的转换:unsafe.Slice(unsafe.StringData(s), len(s)).

  • 这不是活不活的问题。GC 可以移动实时内存。关键是你 a) 将指针转换为 `uintptr` (隐式地,通过将​​其放入分配的 `SliceHeader` 中),然后 b) 将其转换回指针(通过将 `SliceHeader` 转换为切片),之间有一个间隔。在该时间间隔内,GC 可以移动内存而不更新您存储的“uintptr”,或者它可以收集指针,因为不再有指向它的指针。这就是为什么你必须遵守规则。确保没有被指点未被指向的间隔。 (3认同)
  • FTR 我强烈建议你不要使用`unsafe`。它很微妙,在我看来,很明显,您对它周围的这些微妙之处的理解还不足以安全地使用它。需要明确的是:*出于完全相同的原因,我也不使用“不安全”*。我不太了解它的规则,无法轻松使用它。 (2认同)
  • 我详细解释了为什么代码有错误以及 KeepAlive 如何防止该错误被暴露。我认为您还没有阅读我的上一条评论 - 特别是,KeepAlive *不*如何使代码正确。它使值保持活力,但不会阻止它被移动。我之所以关注你的“不安全”使用,是因为你对规则以及 KeepAlive 是什么和不做什么的无知表明你根本不应该使用它。我不是在谈论您在此处发布的代码 - 我是在谈论您的问题表明有关使用“不安全”的资格。 (2认同)