golang 의 장점이 GC 이지만, Windows API 등을 사용하며 Callback 을 사용해야 할 때 참 골치아픈 것이 GC 문제이다.
https://groups.google.com/g/golang-nuts/c/yNis7bQG_rY/m/yaJFoSx1hgIJ
위와 같은 논의들도 많고..
package main
/*
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
extern void goNativeDone(void*);
__attribute__((weak))
void* thread_func(void* p) {
for (int i=0; i<3; i++) {
usleep(500000);
printf("[thread] Value: %08x\n", *((uint32_t*)p));
}
goNativeDone(p);
return NULL;
}
__attribute__((weak))
void worker(unsigned long long p) {
uint32_t *up = (uint32_t*)p;
pthread_t t;
printf("[worker] Value: %p %08x %08x %08x %08x %08x %08x %08x %08x\n", up, up[0], up[1], up[2], up[3], up[4], up[5], up[6], up[7]);
pthread_create(&t, NULL, thread_func, (void*)p);
}
*/
import "C"
import (
"runtime"
"sync"
"time"
"unsafe"
)
type GoObject struct {
Value1 int32
Value2 int32
Buffer [1048576 * 32]byte
ch chan bool
}
func (o *GoObject) keep() {
<-o.ch
}
func dotest() {
obj := &GoObject{
ch: make(chan bool),
}
obj.Value1 = 0x12345678
obj.Value2 = 0x22222222
go obj.keep()
//pinner.Pin(obj)
C.worker(C.ulonglong(uintptr(unsafe.Pointer(obj))))
runtime.GC()
runtime.GC()
time.Sleep(time.Second * 3)
}
//export goNativeDone
func goNativeDone(pointer unsafe.Pointer) {
obj := (*GoObject)(pointer)
close(obj.ch)
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
dotest()
wg.Done()
}()
}
wg.Wait()
}
위의 코드는 동작하지 않는다.
channel 을 이용한 꼼수인데 go-routine 으로 keep() 함수가 살아있음에도 불구하고 GC가 동작해 GoObject 는 제거된다.
(1.21 버전으로 테스트한 결과:)
[worker] Value: 0xc000100000 12345678 22222222 00000000 00000000 00000000 00000000 00000000 00000000
[worker] Value: 0xc002380000 12345678 22222222 00000000 00000000 00000000 00000000 00000000 00000000
[thread] Value: 00000000
[thread] Value: 00000000
[thread] Value: 00000000
[thread] Value: 00000000
[thread] Value: 00000000
panic: close of nil channel
worker 함수까지는 GC가 동작하지 않아서 당연히 데이터가 있지만, worker 함수가 나가고 GC가 동작한 다음, thread 에서 해당 객체를 바라보면 메모리가 제거되어 초기화 된것을 볼 수 있다.
func (o *GoObject) keep() {
<-o.ch
_ = o.ch // 동작 안함
o.Value2 = 2 // 동작 함
}
위와 같이 더미값을 주면 동작하긴 하다..
사실 가장 좋은 방법은 global variable 에 객체를 남겨놓는게 안전할 것이다. 이를 위해서 runtime.Pinner 가 있다.
반응형
댓글