
本文介绍如何在运行 net/http Web 服务的同时,使用 goroutine 和定时器(如 time.Ticker 或成熟调度库)持续执行后台命令(如 date、uptime、ps),并通过通道安全共享结果,避免阻塞主线程。
本文介绍如何在运行 `net/http` web 服务的同时,使用 goroutine 和定时器(如 `time.ticker` 或成熟调度库)持续执行后台命令(如 `date`、`uptime`、`ps`),并通过通道安全共享结果,避免阻塞主线程。
在构建 Go Web 服务时,常需在后台周期性采集系统指标(如时间、负载、进程快照),同时保持 HTTP 接口响应无延迟。原代码仅单次执行命令且未处理并发与错误恢复,无法满足“每几秒持续运行”的需求。以下是推荐的工程化实现方案:
✅ 推荐方案:time.Ticker + 共享状态通道(零依赖、可控性强)
package main
import (
"bytes"
"log"
"net/http"
"os/exec"
"strings"
"sync"
"time"
)
// 全局缓存结果,线程安全
var (
dataMu sync.RWMutex
status = map[string]string{
"date": "",
"uptime": "",
"ps": "",
}
)
// 后台任务:定期执行命令并更新共享状态
func runPeriodic(cmdStr string, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
parts := strings.Fields(cmdStr)
if len(parts) == 0 {
continue
}
cmd := exec.Command(parts[0], parts[1:]...)
var outBuf, errBuf bytes.Buffer
cmd.Stdout, cmd.Stderr = &outBuf, &errBuf
if err := cmd.Run(); err != nil {
log.Printf("Failed to run %q: %v, stderr: %s", cmdStr, err, errBuf.String())
continue
}
dataMu.Lock()
status[cmdStr] = outBuf.String()
dataMu.Unlock()
}
}
func handler(w http.ResponseWriter, r *http.Request) {
dataMu.RLock()
defer dataMu.RUnlock()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
// 简单 JSON 响应(生产环境建议用 encoding/json)
w.Write([]byte(`{"date":"` + status["date"] + `","uptime":"` + status["uptime"] + `","ps":"` + status["ps"] + `"}`))
}
func main() {
// 启动多个后台采集 goroutine(非阻塞)
go runPeriodic("date", 5*time.Second)
go runPeriodic("uptime", 10*time.Second)
go runPeriodic("ps aux | head -n 10", 30*time.Second) // 示例:限制输出行数
// 启动 HTTP 服务(前台)
http.HandleFunc("/metrics", handler)
log.Println("Web server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
⚠️ 关键注意事项:
-
避免
log.Fatal在 goroutine 中使用:它会终止整个进程,应改用log.Printf+ 错误跳过; -
命令超时控制:生产环境务必为
exec.Command添加上下文超时(如exec.CommandContext(context.WithTimeout(...))),防止挂起; -
资源隔离:高频调用
ps可能影响性能,建议通过ps aux --no-headers | head -20限制输出; - 通道替代方案说明:若坚持用通道通信(如原问题所示),需为每个命令维护独立 channel + select 多路复用,但易导致 goroutine 泄漏或死锁;共享状态 + 读写锁更简洁可靠;
-
进阶选择:对复杂调度(如 cron 表达式、失败重试、任务依赖),可引入 gocron 或 robfig/cron —— 但简单轮询场景,标准库
time.Ticker已足够。
该方案确保 Web 服务始终响应迅速,后台任务稳定运行,数据安全共享,符合 Go 的并发哲学与生产实践要求。










