golang 源碼分析:goreman

        https://github.com/mattn/goreman 是 golang 實現的一個進程管理器,方便我們快速啓動多個進程,比如 etcd 就是通過它來管理的,下面我們看下如何使用:

    新建一個文件 Procfile

command1: ls
command2: pwd
command3: echo 'helloworld'

然後,運行命令

% goreman start
18:44:10 command3 | Starting command3 on port 5200
18:44:10 command1 | Starting command1 on port 5000
18:44:10 command2 | Starting command2 on port 5100
18:44:10 command3 | helloworld
18:44:10 command3 | Terminating command3
18:44:10 command2 | /test/goreman/exp1
18:44:10 command2 | Terminating command2
18:44:10 command1 | Procfile
18:44:10 command1 | Terminating command1

看完如何使用後我們分析下它的源碼:入口文件位於 main.go,首先是加載配置文件,然後切換到 base 目錄,然後就是根據提示執行命令,我們重點看下 start

func main() {
cfg := readConfig()
  if cfg.BaseDir != "" {
    err = os.Chdir(cfg.BaseDir)
cmd := cfg.Args[0]
  switch cmd {
  case "check":
    err = check(cfg)
  case "help":
    usage()
  case "run":
    if len(cfg.Args) >= 2 {
      cmd, args := cfg.Args[1], cfg.Args[2:]
      err = run(cmd, args, cfg.Port)
    } else {
      usage()
    }
  case "export":
    if len(cfg.Args) == 3 {
      format, path := cfg.Args[1], cfg.Args[2]
      err = export(cfg, format, path)
    } else {
      usage()
    }
  case "start":
    c := notifyCh()
    err = start(context.Background(), c, cfg)
  case "version":
    showVersion()
  default:
    usage()
  }
// filename of Procfile.
var procfile = flag.String("f", "Procfile", "proc file")
func readConfig() *config {
  var cfg config
  flag.Parse()
  if flag.NArg() == 0 {
    usage()
  }
  cfg.Procfile = *procfile

        首先是監聽命令行的信號

func notifyCh() <-chan os.Signal {
  sc := make(chan os.Signal, 10)
  signal.Notify(sc, sigterm, sigint, sighup)
  return sc
}

然後通過 chan 傳給 start 函數

err = start(context.Background(), c, cfg)

首先解析我們的 Procfile

func readProcfile(cfg *config) error {
  content, err := os.ReadFile(cfg.Procfile)
  for _, line := range strings.Split(string(content), "\n") {
    tokens := strings.SplitN(line, ":", 2)
    proc := &procInfo{name: k, cmdline: v, colorIndex: index}
    if *setPorts {
      proc.setPort = true
      proc.port = cfg.BasePort
      cfg.BasePort += 100
    }
    proc.cond = sync.NewCond(&proc.mu)

然後查找 proc,分類啓動命令

func start(ctx context.Context, sig <-chan os.Signal, cfg *config) error {
  err := readProcfile(cfg)
  for _, v := range cfg.Args[1:] {
      proc := findProc(v)
 if *startRPCServer {
    go startServer(ctx, rpcChan, cfg.Port)
  }
  procsErr := startProcs(sig, rpcChan, cfg.ExitOnError)

啓動 rpc 的代碼位於 rpc.go

func startServer(ctx context.Context, rpcChan chan<- *rpcMessage, listenPort uint) error {
rpc.Register(gm)
  server, err := net.Listen("tcp", fmt.Sprintf("%s:%d", defaultAddr(), listenPort))

啓動其它進程的代碼位於 proc.go

func startProcs(sc <-chan os.Signal, rpcCh <-chan *rpcMessage, exitOnError bool) error {
for _, proc := range procs {
    startProc(proc.name, &wg, errCh)
  }
func startProc(name string, wg *sync.WaitGroup, errCh chan<- error) error {
  proc := findProc(name)
 go func() {
    spawnProc(name, errCh)
    if wg != nil {
      wg.Done()
    }
    proc.mu.Unlock()
  }()
func spawnProc(name string, errCh chan<- error) {
  proc := findProc(name)
  logger := createLogger(name, proc.colorIndex)
  cs := append(cmdStart, proc.cmdline)
  cmd := exec.Command(cs[0], cs[1:]...)
  cmd.Stdin = nil
  cmd.Stdout = logger
  cmd.Stderr = logger
  cmd.SysProcAttr = procAttrs

最終調用

cmd := exec.Command(

執行命令。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/seyqZXFJrZZc4OAfdokvkw