面试题答案
一键面试package main
import (
"fmt"
"log"
"os"
"os/exec"
"syscall"
"time"
)
func main() {
// 判断是否已经是守护进程
if len(os.Args) > 1 && os.Args[1] == "daemon" {
runDaemon()
return
}
// 创建守护进程
args := append([]string{"daemon"}, os.Args[1:]...)
cmd := exec.Command(os.Args[0], args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}
err := cmd.Start()
if err != nil {
log.Fatalf("Failed to start daemon: %v", err)
}
fmt.Println("Daemon started successfully.")
}
func runDaemon() {
// 关闭标准输入输出
stdin, _ := os.Open(os.DevNull)
os.Stdin = stdin
stdout, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0644)
os.Stdout = stdout
stderr, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0644)
os.Stderr = stderr
for {
// 定期执行任务,每10分钟写入一条日志到文件
writeLog()
time.Sleep(10 * time.Minute)
}
}
func writeLog() {
file, err := os.OpenFile("daemon.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Printf("Failed to open log file: %v", err)
return
}
defer file.Close()
_, err = file.WriteString(fmt.Sprintf("%s - Log message\n", time.Now().Format(time.RFC3339)))
if err != nil {
log.Printf("Failed to write to log file: %v", err)
}
}
资源管理
- 文件描述符:在创建守护进程时,将标准输入、输出和错误输出重定向到
/dev/null
(在Windows下使用等效操作),并在打开日志文件时及时关闭文件描述符,通过defer file.Close()
确保在函数结束时关闭文件。 - 内存:Go语言的垃圾回收机制(GC)自动管理内存,在代码中合理使用局部变量,避免在循环中不断创建大量的对象而不释放。
不同操作系统下的差异及处理
- Linux:使用
syscall.Setsid
来创建新的会话和进程组,从而使进程脱离终端控制成为守护进程。标准输入输出重定向到/dev/null
。 - Windows:Windows没有像Linux那样原生的守护进程概念。一种常见的做法是使用Windows服务来模拟守护进程的行为。可以使用一些工具(如
NSSM - the Non-Sucking Service Manager
)将Go程序注册为Windows服务,在代码层面需要处理好服务启动、停止等生命周期事件。在重定向标准输入输出时,使用nul
文件(Windows下等同于/dev/null
)。
例如,使用NSSM
将Go程序注册为Windows服务的步骤:
- 下载
NSSM
并解压。 - 打开命令行,进入
NSSM
所在目录。 - 执行
nssm install your_service_name your_go_program_path
来安装服务。 - 可以通过
nssm start your_service_name
和nssm stop your_service_name
来启动和停止服务。