[{"data":1,"prerenderedAt":347},["ShallowReactive",2],{"post-\u002Fposts\u002Fgolang-exec-kill":3,"surround-\u002Fposts\u002Fgolang-exec-kill":338},{"id":4,"title":5,"body":6,"cover":323,"date":324,"description":325,"draft":326,"extension":327,"meta":328,"minutes":33,"navigation":329,"path":330,"seo":331,"stem":332,"tags":333,"updated":323,"__hash__":337},"posts\u002Fposts\u002Fgolang-exec-kill.md","Golang exec.Cmd在Kill后不能关闭进程",{"type":7,"value":8,"toc":321},"minimark",[9,13,158,161,164,314,317],[10,11,12],"p",{},"出现的场景，在golang项目中需要管理shell脚本的执行，主要实现关闭进程的代码如下：",[14,15,20],"pre",{"className":16,"code":17,"language":18,"meta":19,"style":19},"language-go shiki shiki-themes tokyo-night github-light","func (s *Streamer) Kill() error {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    \u002F\u002F 首先关闭输出流\n    if closer, ok := s.Cmd.Stdout.(io.Closer); ok {\n        if err := closer.Close(); err != nil {\n            log.Printf(\"Failed to close stdout: %v\", err)\n        }\n    }\n    if closer, ok := s.Cmd.Stderr.(io.Closer); ok {\n        if err := closer.Close(); err != nil {\n            log.Printf(\"Failed to close stderr: %v\", err)\n        }\n    }\n    if s.Cmd.Process != nil {\n        \u002F\u002F Kill the process\n        if err := s.Cmd.Process.Kill(); err != nil {\n            return err\n        }\n    }\n    \u002F\u002F 等待进程关闭\n    return s.Cmd.Wait()\n}\n","go","",[21,22,23,31,37,43,49,55,61,67,73,79,85,90,96,101,106,112,118,124,130,135,140,146,152],"code",{"__ignoreMap":19},[24,25,28],"span",{"class":26,"line":27},"line",1,[24,29,30],{},"func (s *Streamer) Kill() error {\n",[24,32,34],{"class":26,"line":33},2,[24,35,36],{},"    s.mu.Lock()\n",[24,38,40],{"class":26,"line":39},3,[24,41,42],{},"    defer s.mu.Unlock()\n",[24,44,46],{"class":26,"line":45},4,[24,47,48],{},"    \u002F\u002F 首先关闭输出流\n",[24,50,52],{"class":26,"line":51},5,[24,53,54],{},"    if closer, ok := s.Cmd.Stdout.(io.Closer); ok {\n",[24,56,58],{"class":26,"line":57},6,[24,59,60],{},"        if err := closer.Close(); err != nil {\n",[24,62,64],{"class":26,"line":63},7,[24,65,66],{},"            log.Printf(\"Failed to close stdout: %v\", err)\n",[24,68,70],{"class":26,"line":69},8,[24,71,72],{},"        }\n",[24,74,76],{"class":26,"line":75},9,[24,77,78],{},"    }\n",[24,80,82],{"class":26,"line":81},10,[24,83,84],{},"    if closer, ok := s.Cmd.Stderr.(io.Closer); ok {\n",[24,86,88],{"class":26,"line":87},11,[24,89,60],{},[24,91,93],{"class":26,"line":92},12,[24,94,95],{},"            log.Printf(\"Failed to close stderr: %v\", err)\n",[24,97,99],{"class":26,"line":98},13,[24,100,72],{},[24,102,104],{"class":26,"line":103},14,[24,105,78],{},[24,107,109],{"class":26,"line":108},15,[24,110,111],{},"    if s.Cmd.Process != nil {\n",[24,113,115],{"class":26,"line":114},16,[24,116,117],{},"        \u002F\u002F Kill the process\n",[24,119,121],{"class":26,"line":120},17,[24,122,123],{},"        if err := s.Cmd.Process.Kill(); err != nil {\n",[24,125,127],{"class":26,"line":126},18,[24,128,129],{},"            return err\n",[24,131,133],{"class":26,"line":132},19,[24,134,72],{},[24,136,138],{"class":26,"line":137},20,[24,139,78],{},[24,141,143],{"class":26,"line":142},21,[24,144,145],{},"    \u002F\u002F 等待进程关闭\n",[24,147,149],{"class":26,"line":148},22,[24,150,151],{},"    return s.Cmd.Wait()\n",[24,153,155],{"class":26,"line":154},23,[24,156,157],{},"}\n",[10,159,160],{},"在前端调用接口触发方法Kill时发现时不时出现接口请求超时，一开始以为是网络的问题，后面经过过排查，发现Cmd.Wait()一直没有结束，导致接口请求超时。在查找资料的过程中发现，Cmd.Wait()会一直等待进程结束，如果进程一直没有结束，那么Cmd.Wait()也会一直等待，然而实际情况是shell已经执行完毕，经过排查是产生了僵尸子进程。",[10,162,163],{},"为了解决这个问题，对代码做了一些修改，首先启动一个 goroutine 来处理超时，然后Cmd.Wait()不能无限制等待，可以通过time.After设置一个超时时间，如果超过这个时间还没有结束，将通过发送 SIGTERM 信号，具体实现如下：",[14,165,167],{"className":16,"code":166,"language":18,"meta":19,"style":19},"func (s *Streamer) Stop() error {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    \u002F\u002F ...\n    \u002F\u002F 设置超时时间\n    timeout := time.Duration(5 * time.Second)\n    done := make(chan error, 1)\n    \u002F\u002F 启动一个 goroutine 来处理超时\n    go func() {\n        err := s.Cmd.Wait()\n        done \u003C- err\n    }()\n    \u002F\u002F 等待命令完成或超时\n    select {\n    case err := \u003C-done:\n        if err != nil {\n            fmt.Println(\"命令执行失败:\", err)\n        } else {\n            fmt.Println(\"命令执行成功\")\n        }\n    case \u003C-time.After(timeout):\n        \u002F\u002F 发送 SIGTERM 信号\n        if err := exec.Command(\"kill\", \"-SIGTERM\", fmt.Sprintf(\"-%d\", s.Cmd.Process.Pid)).Run(); err != nil {\n            fmt.Println(\"发送 SIGTERM 信号失败:\", err)\n        }\n        fmt.Println(\"命令超时，已终止\")\n    }\n    return nil\n}\n",[21,168,169,174,178,182,187,192,197,202,207,212,217,222,227,232,237,242,247,252,257,262,266,271,276,281,287,292,298,303,309],{"__ignoreMap":19},[24,170,171],{"class":26,"line":27},[24,172,173],{},"func (s *Streamer) Stop() error {\n",[24,175,176],{"class":26,"line":33},[24,177,36],{},[24,179,180],{"class":26,"line":39},[24,181,42],{},[24,183,184],{"class":26,"line":45},[24,185,186],{},"    \u002F\u002F ...\n",[24,188,189],{"class":26,"line":51},[24,190,191],{},"    \u002F\u002F 设置超时时间\n",[24,193,194],{"class":26,"line":57},[24,195,196],{},"    timeout := time.Duration(5 * time.Second)\n",[24,198,199],{"class":26,"line":63},[24,200,201],{},"    done := make(chan error, 1)\n",[24,203,204],{"class":26,"line":69},[24,205,206],{},"    \u002F\u002F 启动一个 goroutine 来处理超时\n",[24,208,209],{"class":26,"line":75},[24,210,211],{},"    go func() {\n",[24,213,214],{"class":26,"line":81},[24,215,216],{},"        err := s.Cmd.Wait()\n",[24,218,219],{"class":26,"line":87},[24,220,221],{},"        done \u003C- err\n",[24,223,224],{"class":26,"line":92},[24,225,226],{},"    }()\n",[24,228,229],{"class":26,"line":98},[24,230,231],{},"    \u002F\u002F 等待命令完成或超时\n",[24,233,234],{"class":26,"line":103},[24,235,236],{},"    select {\n",[24,238,239],{"class":26,"line":108},[24,240,241],{},"    case err := \u003C-done:\n",[24,243,244],{"class":26,"line":114},[24,245,246],{},"        if err != nil {\n",[24,248,249],{"class":26,"line":120},[24,250,251],{},"            fmt.Println(\"命令执行失败:\", err)\n",[24,253,254],{"class":26,"line":126},[24,255,256],{},"        } else {\n",[24,258,259],{"class":26,"line":132},[24,260,261],{},"            fmt.Println(\"命令执行成功\")\n",[24,263,264],{"class":26,"line":137},[24,265,72],{},[24,267,268],{"class":26,"line":142},[24,269,270],{},"    case \u003C-time.After(timeout):\n",[24,272,273],{"class":26,"line":148},[24,274,275],{},"        \u002F\u002F 发送 SIGTERM 信号\n",[24,277,278],{"class":26,"line":154},[24,279,280],{},"        if err := exec.Command(\"kill\", \"-SIGTERM\", fmt.Sprintf(\"-%d\", s.Cmd.Process.Pid)).Run(); err != nil {\n",[24,282,284],{"class":26,"line":283},24,[24,285,286],{},"            fmt.Println(\"发送 SIGTERM 信号失败:\", err)\n",[24,288,290],{"class":26,"line":289},25,[24,291,72],{},[24,293,295],{"class":26,"line":294},26,[24,296,297],{},"        fmt.Println(\"命令超时，已终止\")\n",[24,299,301],{"class":26,"line":300},27,[24,302,78],{},[24,304,306],{"class":26,"line":305},28,[24,307,308],{},"    return nil\n",[24,310,312],{"class":26,"line":311},29,[24,313,157],{},[10,315,316],{},"以上代码可能并不完善，仅提供一个思路，具体实现还需要根据实际情况进行调整。",[318,319,320],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":19,"searchDepth":39,"depth":39,"links":322},[],null,"2024-03-17","当前基于Golang 1.22.2 版本实现",false,"md",{},true,"\u002Fposts\u002Fgolang-exec-kill",{"title":5,"description":325},"posts\u002Fgolang-exec-kill",[334,335,336],"golang","exec","kill","X6TDcwDyK4FqP49Z9zKWnedzl71b16jo1Kokx6IKK-0",[339,343],{"title":340,"path":341,"stem":342,"children":-1},"Golang在linux跨区移动文件失败","\u002Fposts\u002Fgolang-linux-file","posts\u002Fgolang-linux-file",{"title":344,"path":345,"stem":346,"children":-1},"Golang基于Redis生成唯一序列","\u002Fposts\u002Fgolang-redis-seq","posts\u002Fgolang-redis-seq",1786707084689]