我有一个项目,需要使用 C 库加载大图像,并且我想避免将数据复制到 Go 托管内存中。为此,我使用
godata := unsafe.Slice((*uint8)(cdata), size)
创建一个指向非托管内存的切片。
然后我调用
runtime.SetFinalizer(&godata, free)
来允许在切片被 GC 时释放内存。
我的问题是在最后一次使用
godata
之前调用终结器,导致段错误。
package main
/*
#include <stdlib.h>
unsigned char* allocate(int size) {
return (unsigned char*)malloc(size);
}
void free_allocated(unsigned char *c) {
free(c);
}
*/
import "C"
import (
"runtime"
"time"
"unsafe"
)
type dummy struct {
Data []uint8
}
func allocate(size int) *dummy {
cdata := C.allocate((C.int)(size))
godata := unsafe.Slice((*uint8)(cdata), size)
runtime.SetFinalizer(&godata, func(data *[]uint8) {
println("Freeing")
C.free_allocated(cdata)
})
// The issue does not occur without wrapping godata in a Dummy struct
return &dummy{Data: godata}
}
func main() {
println("Start")
dummy := allocate(1_000_000_000)
dummy.Data[100_000_000] = 1
println("Allocated")
println("GC")
runtime.GC()
// Wait to let the system reclaim the memory properly
println("Wainting 10 seconds")
<-time.After(10 * time.Second)
// dummy.Data is already freed at this point, causing a fault
println("Value is: ", dummy.Data[100_000_000])
}
对我来说,“Freeing”是在“GC”之后打印的。
我的问题是如何正确使用
SetFinalizer
,这样就不会发生这个问题。
我发现了一个似乎有效的技巧,但可能并不总是有效,所以我不会将此答案标记为已接受。不要在生产中使用此代码!!
func allocate(size int) *dummy {
cdata := C.allocate((C.int)(size))
goslice := unsafe.Slice((*uint8)(cdata), size)
// An empty struct has zero bytes, so only the memory for the pointer should be allocated.
// 'ptr' is a pointer into go managed memory, so SetFinalizer won't complain
ptr := new(struct{})
runtime.SetFinalizer(ptr, func(_ *struct{}) {
println("Freeing")
C.free_allocated(cdata)
})
// Changing the address of the pointer in this way 'tricks' the compiler.
// See https://github.com/golang/go/issues/58625#issuecomment-1642687911
// This effectively makes the new address managed, as I understand it
*(*uintptr)(unsafe.Pointer(&ptr)) = uintptr(unsafe.Pointer(cdata))
return &dummy{Data: goslice}
}