annidy / notes

0 stars 0 forks source link

SetFinalizer #292

Open annidy opened 1 month ago

annidy commented 1 month ago

SetFinalizer提供了一个机制:当对象即将被垃圾回收时,调用一个回调函数。

package main

import (
    "fmt"
    "runtime"
    "runtime/debug"
    "time"
)

type MyStruct struct {
    value int
}

// finalizer 是一个将被作为终结器调用的函数
func finalizer(ms *MyStruct) {
    fmt.Println("Finalizing MyStruct:", ms.value)
}

func main() {
    // 设置垃圾回收器的百分比。很有必要,不然默认是100%,很难触发垃圾回收
    debug.SetGCPercent(10)

    // 创建一个MyStruct实例
    ms := &MyStruct{value: 42}

    // 为MyStruct实例设置终结器
    runtime.SetFinalizer(ms, finalizer)

    // 将ms设置为nil,使其不再可达
    ms = nil

    // 强制触发垃圾回收
    runtime.GC()

    // 等待一段时间,让垃圾回收器和终结器有机会运行
    time.Sleep(time.Second)
}

这听起来很像是C++的析构函数,但是现实中很少看到有人这样使用。 一个很大的问题是,当对象不可达时,被GC回收的时机是不可控的。可能goroutine退出了都还没回收;有时候对象的作用域还没结束,但是因为后面没有引用,而立即被回收也可能发生

For example, if p points to a struct, such as os.File, that contains a file descriptor d, and p has a finalizer that closes that file descriptor, and if the last use of p in a function is a call to syscall.Write(p.d, buf, size), then p may be unreachable as soon as the program enters syscall.Write. The finalizer may run at that moment, closing p.d, causing syscall.Write to fail because it is writing to a closed file descriptor (or, worse, to an entirely different file descriptor opened by a different goroutine). To avoid this problem, call KeepAlive(p) after the call to syscall.Write.

标准库os.File都不敢用这种方式关闭文件,只能老老实实用defer。 因此,SetFinalizer建议只在追踪代码时使用,不建议放逻辑相关的代码