Skip to main content

golang_gc 相关问题

· 4 min read

golang gc 关闭fd

4月还是5月的时候写了个golang 的程序,因为要保证最多只有一个进程存在所以进程启动就去获取锁,没有获取文件锁的进程就退出。每分钟我会启动一次进程。目的就是为了进程保活。

使用文件锁就是为了他的特性:

  • 如果文件关闭,那么锁也会被回收

遇到的问题

  • 问题是:过了半天之后ps aux 看启动的进程,发现居然有7-8个。按照预想应该只有一个。 代码大概是长这样的
func lockFile(){
name := "lockfiletest.lock"
file, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_RDWR|syscall.O_CLOEXEC, 0666) //①打开文件
 ...
err = syscall.FcntlFlock(file.Fd(), syscall.F_SETLK, &flockT)  //②加锁
...
}
func main(){
err :=lockFile()
if err!=nil{
os.Exit(2) // ③加锁失败退出
}
}

很简单的逻辑,就是获取文件锁,获取失败则退出

找问题

  • 问题出在哪里呢?  

想了很久很久:难道是我用的库哪里fork了进程?文件被哪个第三方包关闭了?

想了很久很久一直怀疑第三方包有问题,但是最后经过google很多次后定位到是gc 的问题。

相关链接

在下面的例子里面编译后会在手动执行runtime.GC()后文件被回收

package main

import (
"os"
"log"
"time"
"runtime"
)

func openFile(path string) error {
_, err := os.Open(path)
return err
}

func main() {
if err := openFile(os.Args[1]); err != nil {
log.Fatal(err)
}
// trigger GC below will also recycle the non-referenced fd opened before
runtime.GC()
time.Sleep(time.Hour)
}
  • 怎么看进程打开的文件呢?  

    通过proc文件系统就可以了,proc文件系统几乎把linux内核所有的统计量都导出来了哦

## 8808 就是我的nginx 的master 的pid
ll /proc/8808/fd/
total 0
dr-x------ 2 root root 0 8月 10 06:16 ./
dr-xr-xr-x 9 root root 0 8月 9 07:35 ../
lrwx------ 1 root root 64 8月 10 06:16 0 -> /dev/null
lrwx------ 1 root root 64 8月 10 06:16 1 -> /dev/null
l-wx------ 1 root root 64 8月 10 06:16 2 -> /usr/local/nginx/logs/error.log*
lrwx------ 1 root root 64 8月 10 06:16 3 -> socket:[78178946]
l-wx------ 1 root root 64 8月 10 06:16 4 -> /usr/local/nginx/logs/access.log*
l-wx------ 1 root root 64 8月 10 06:16 5 -> /usr/local/nginx/logs/error.log*
lrwx------ 1 root root 64 8月 10 06:16 6 -> socket:[78180730]
lrwx------ 1 root root 64 8月 10 06:16 7 -> socket:[78178947]

怎么解决

第一:我们的问题是什么?   其实问题很简单:

  • 我们的fd这个对象被回收了
  • gc的调用fd对象回调函数
  • 回调函数把fd对象对应的文件描述符关闭了

解决方案:

把fd 弄成全局变量,全局变量一直被引用所以不会被gc回收掉

var file *File  // 加了一行变成全局变量
func lockFile(){
name := "lockfiletest.lock"
file, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_RDWR|syscall.O_CLOEXEC, 0666) //①打开文件
 ...
err = syscall.FcntlFlock(file.Fd(), syscall.F_SETLK, &flockT)  //②加锁
...
}